/** * MDS JS lib for MiniDAPPs.. * * VERSION : 2.1.0 * * @spartacusrex */ /** * The MAIN Minima Callback function */ var MDS_MAIN_CALLBACK = null; /** * API call id array */ var API_CALLS = []; /** * Main MINIMA Object for all interaction */ var MDS = { //Main File host filehost : "", //RPC Host for Minima mainhost : "", //The MiniDAPP UID minidappuid : "", //Is logging RPC enabled logging : false, //When debuggin you can hard set the Host and port DEBUG_HOST : null, DEBUG_PORT : -1, //An allowed TEST Minidapp ID for SQL - can be overridden DEBUG_MINIDAPPID : "0x00", /** * Minima Startup - with the callback function used for all Minima messages */ init : function(callback){ //Log a little.. MDS.log("Initialising Internal MDS.."); //Is logging enabled.. via the URL if(MDS.form.getParams("MDS_LOGGING") != null){ MDS.logging = true; } //Get the host and port.. var host = window.location.hostname; var port = Math.floor(window.location.port); //Get ther MiniDAPP UID MDS.minidappuid = MDS.form.getParams("uid"); //HARD SET if debug mode - running from a file if(MDS.DEBUG_HOST != null){ MDS.log("DEBUG Settings Found.."); host=MDS.DEBUG_HOST; port=MDS.DEBUG_PORT; } if(MDS.minidappuid == null){ MDS.minidappuid = MDS.DEBUG_MINIDAPPID; } //Is one specified.. if(MDS.minidappuid == "0x00"){ MDS.log("No MiniDAPP UID specified.. using test value"); } //Are we HTTP or HTTPS if(window.location.protocol.startsWith("https")){ if(port == 0){ MDS.filehost = "https://"+host+"/"; MDS.mainhost = "https://"+host+"/mdscommand_/"; }else{ MDS.filehost = "https://"+host+":"+port+"/"; MDS.mainhost = "https://"+host+":"+port+"/mdscommand_/"; } }else{ if(port == 0){ MDS.filehost = "http://"+host+"/"; MDS.mainhost = "http://"+host+"/mdscommand_/"; }else{ MDS.filehost = "http://"+host+":"+port+"/"; MDS.mainhost = "http://"+host+":"+port+"/mdscommand_/"; } } MDS.log("MDS HOST : "+MDS.filehost); //Store this for poll messages MDS_MAIN_CALLBACK = callback; //Run the POll Listener ONCE - to get the latest Poll Counter.. PollListener(); //Now call it every ~2 secs.. setInterval(function(){PollListener();},2500); //And Post a message MDSPostMessage({ "event": "inited" }); }, /** * Log some data with a timestamp in a consistent manner to the console */ log : function(output){ console.log("Minima @ "+new Date().toLocaleString()+" : "+output); }, /** * Notify the User - on Phone it pops up in status bar. On desktop appears in Logs */ notify : function(output){ //Send via POST httpPostAsync("notify", output); }, /** * Cancel this MiniDAPPs notification */ notifycancel : function(){ //Send via POST httpPostAsync("notifycancel", "*"); }, /** * Runs a function on the Minima Command Line - same format as MInima */ cmd : function(command, callback){ //Send via POST httpPostAsync("cmd", command, callback); }, /** * Runs a SQL command on this MiniDAPPs SQL Database */ sql : function(command, callback){ //Send via POST httpPostAsync("sql", command, callback); }, /** * Get a link to a different Dapp. READ dapps can only get READ DAPPS. WRITE can get all dapps. */ dapplink : function(dappname, callback){ //Send via POST httpPostAsync("dapplink", dappname, function(result){ var linkdata = {}; linkdata.status = result.status; //Create the link.. if(result.status){ linkdata.uid = result.response.uid; linkdata.sessionid = result.response.sessionid; linkdata.base = MDS.filehost+linkdata.uid+"/index.html?uid="+result.response.sessionid; }else{ //Not found.. linkdata.error = result.error; } callback(linkdata); }); }, /** * API call to a different minidapp */ api : { call : function(dappname, data, callback){ //Construct a unique API request var rand = ""+Math.random()*1000000000; //Construct a callback list object var callitem = {}; callitem.id = rand; callitem.callback = callback; //Add to the api calls.. API_CALLS.push(callitem); //Create the single line var commsline = dappname+"&request&"+rand+"&"+data; //Send via POST httpPostAsync("api", commsline); }, reply : function(dappname, id, data, callback){ //Create the single line var commsline = dappname+"&response&"+id+"&"+data; //Send via POST httpPostAsync("api", commsline, callback); } }, /** * Network Commands */ net : { /** * Make a GET request */ GET : function(url, callback){ //Send via POST httpPostAsync("net", url, callback); }, /** * Make a GET request WITH a Basic Auth token header */ GETAUTH : function(url, basicauth, callback){ //Create the single line var commsline = url+"&"+basicauth; //Send via POST httpPostAsync("netauth", commsline, callback); }, /** * Make a POST request */ POST : function(url, data, callback){ //Create the sinlg eline version.. var postline = url+"&"+data; //Send via POST httpPostAsync("netpost", postline, callback); } }, /** * Simple GET and SET key value pairs that are saved persistently */ keypair : { /** * GET a value */ get : function(key, callback){ //Create the single line var commsline = "get&"+key; //Send via POST httpPostAsync("keypair", commsline, callback); }, /** * SET a value */ set : function(key, value, callback){ //Create the single line var commsline = "set&"+key+"&"+value; //Send via POST httpPostAsync("keypair", commsline, callback); } }, /** * COMMS - send a message to ALL minidapps or JUST your own service.js */ comms : { /** * PUBLIC message broadcast to ALL (callback is optional) */ broadcast : function(msg, callback){ //Create the single line var commsline = "public&"+msg; //Send via POST httpPostAsync("comms", commsline, callback); }, /** * PRIVATE message send just to this MiniDAPP (callback is optional) */ solo : function(msg, callback){ //Create the single line var commsline = "private&"+msg; //Send via POST httpPostAsync("comms", commsline, callback); } }, /** * File access */ file : { /** * List file in a folder .. start at / */ list : function(folder, callback){ //Create the single line var commsline = "list&"+folder; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Save text - can be text, a JSON in string format or hex encoded data */ save : function(filename, text, callback){ //Create the single line var commsline = "save&"+filename+"&"+text; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Save Binary Data - supply as a HEX string */ savebinary : function(filename, hexdata, callback){ //Create the single line var commsline = "savebinary&"+filename+"&"+hexdata; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Load text - can be text, a JSON in string format or hex encoded data */ load : function(filename, callback){ //Create the single line var commsline = "load&"+filename; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Load Binary data - returns the HEX data */ loadbinary : function(filename, callback){ //Create the single line var commsline = "loadbinary&"+filename; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Delete a file */ delete : function(filename, callback){ //Create the single line var commsline = "delete&"+filename; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Get the full path - if you want to run a command on the file / import a txn / unsigned txn etc */ getpath : function(filename, callback){ //Create the single line var commsline = "getpath&"+filename; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Make a directory */ makedir : function(filename, callback){ //Create the single line var commsline = "makedir&"+filename; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Copy a file */ copy : function(filename, newfilename, callback){ //Create the single line var commsline = "copy&"+filename+"&"+newfilename; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Move a file */ move : function(filename, newfilename, callback){ //Create the single line var commsline = "move&"+filename+"&"+newfilename; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Download a File from the InterWeb - Will be put in Downloads folder */ download : function(url, callback){ //Create the single line var commsline = "download&"+url; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Upload a file in chunks to the /fileupload folder */ upload : function(file, callback){ //Start the file recursion.. _recurseUploadMDS(file,0,callback); }, /** * List file in a folder .. start at / */ listweb : function(folder, callback){ //Create the single line var commsline = "listweb&"+folder; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Copy a file to your web folder */ copytoweb : function(file, webfile, callback){ //Create the single line var commsline = "copytoweb&"+file+"&"+webfile; //Send via POST httpPostAsync("file", commsline, callback); }, /** * Delete a file or folder from web folder */ deletefromweb : function(file, callback){ //Create the single line var commsline = "deletefromweb&"+file; //Send via POST httpPostAsync("file", commsline, callback); } }, /** * Function for GET parameters.. */ form : { //Return the GET parameter by scraping the location.. getParams : function(parameterName){ var result = null, tmp = []; var items = window.location.search.substr(1).split("&"); for (var index = 0; index < items.length; index++) { tmp = items[index].split("="); //console.log("TMP:"+tmp); if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]); } return result; } }, /** * UTILITY functions.. very useful */ util : { //Convert HEX to Base 64 - removes the 0x if necessary hexToBase64(hexstring) { //Check if starts with 0x var thex = hexstring; if(hexstring.startsWith("0x")){ thex = hexstring.substring(2); } return btoa(thex.match(/\w{2}/g).map(function(a) { return String.fromCharCode(parseInt(a, 16)); }).join("")); }, //Convert Base64 to HEX base64ToHex(str) { const raw = atob(str); let result = ''; for (let i = 0; i < raw.length; i++) { const hex = raw.charCodeAt(i).toString(16); result += (hex.length === 2 ? hex : '0' + hex); } return result.toUpperCase(); }, //Convert Base64 to a Uint8Array - useful for Blobs base64ToArrayBuffer(base64) { var binary_string = window.atob(base64); var len = binary_string.length; var bytes = new Uint8Array(len); for (var i = 0; i < len; i++) { bytes[i] = binary_string.charCodeAt(i); } return bytes.buffer; }, //Return a state variable given the coin getStateVariable(coin,port){ //Get the state vars var statvars = coin.state; var len = statvars.length; for (var i = 0; i < len; i++) { var state = statvars[i]; if(state.port == port){ return state.data; } } return undefined; } } }; /** * Post a message to the Minima Event Listeners */ function MDSPostMessage(json){ //And dispatch if(MDS_MAIN_CALLBACK){ //Is this an API response call.. if(json.event == "MDSAPI"){ //Check if it is a response.. if(!json.data.request){ //Find the API CALL Object var found = ""; var len = API_CALLS.length; for(var i=0;i= 200 && status < 400)) { //Do we log it.. if(MDS.logging){ MDS.log("RESPONSE:"+xmlHttp.responseText); } //Send it to the callback function.. if(callback){ callback(JSON.parse(xmlHttp.responseText)); } }else{ //Some error.. postMDSFail(finalurl,params,xmlHttp.status); } } } xmlHttp.open("POST", finalurl, true); // true for asynchronous xmlHttp.overrideMimeType('text/plain; charset=UTF-8'); xmlHttp.send(encodeURIComponent(params)); //xmlHttp.onerror = function () { // console.log("** An error occurred during the transaction"); //}; } /** * POLLING Call */ function httpPostAsyncPoll(theUrl, params, callback){ //Do we log it.. if(MDS.logging){ MDS.log("POST_POLL_RPC:"+theUrl+" PARAMS:"+params); } var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { var status = xmlHttp.status; if (xmlHttp.readyState == XMLHttpRequest.DONE){ if (status === 0 || (status >= 200 && status < 400)) { //Do we log it.. if(MDS.logging){ MDS.log("RESPONSE:"+xmlHttp.responseText); } //Send it to the callback function.. if(callback){ callback(JSON.parse(xmlHttp.responseText)); } }else{ //Some error.. postMDSFail(theUrl,params,xmlHttp.status); } } } xmlHttp.addEventListener('error', function(ev){ MDS.log("Error Polling - reconnect in 10s"); //setTimeout(function(){PollListener();},10000); }); xmlHttp.open("POST", theUrl, true); // true for asynchronous xmlHttp.overrideMimeType('text/plain; charset=UTF-8'); xmlHttp.send(encodeURIComponent(params)); } /** * Internal recursive function for file upload */ function _recurseUploadMDS(thefullfile, chunk, callback){ //Get some details var filename = thefullfile.name; var filesize = thefullfile.size; //1MB MAX Chunk size.. var chunk_size = 1024 * 1024; var allchunks = Math.ceil(filesize / chunk_size); //Have we finished.. if(chunk>allchunks-1){ return; } var startbyte = chunk_size * chunk; var endbyte = startbyte + chunk_size; if(endbyte>filesize){ endbyte = filesize; } //Get a piece of the file var filepiece = thefullfile.slice(startbyte,endbyte); //Create a form.. var formdata = new FormData(); formdata.append("uid", MDS.minidappuid); //Filedata handled a little differently formdata.append("filename", filename); formdata.append("filesize", filesize); formdata.append("allchunks", allchunks); formdata.append("chunknum", chunk); formdata.append("fileupload", filepiece); var request = new XMLHttpRequest(); request.open("POST", "/fileuploadchunk.html"); request.onreadystatechange = function() { var status = request.status; if (request.readyState == XMLHttpRequest.DONE){ if (status === 0 || (status >= 200 && status < 400)) { //Send it to the callback function.. if(callback){ var resp = {}; resp.status = true; resp.filename = filename; resp.size = filesize; resp.allchunks = allchunks; resp.chunk = chunk+1; resp.start = startbyte; resp.end = endbyte; callback(resp); } //And now continue uploading.. if(callback){ _recurseUploadMDS(thefullfile,chunk+1, callback); }else{ _recurseUploadMDS(thefullfile,chunk+1); } }else{ if(callback){ var resp = {}; resp.status = false; resp.error = request.responseText; resp.filename = filename; resp.size = filesize; resp.allchunks = allchunks; resp.chunk = chunk; resp.start = startbyte; resp.end = endbyte; callback(resp); } //Some error.. MDS.log("MDS FILEUPLOAD CHUNK ERROR: "+request.responseText); } } } //And finally send the POST request request.send(formdata); }