forked from sairajzero/flo-standard-operations
/
floDapps.min.js
1 lines (1 loc) · 20.1 KB
/
floDapps.min.js
1
!function(EXPORTS){"use strict";const floDapps="object"===typeof module?module.exports:window.floDapps={},DEFAULT={root:"floDapps",application:floGlobals.application,adminID:floGlobals.adminID};var user_priv_raw,aes_key,user_priv_wrap;Object.defineProperties(floDapps,{application:{get:()=>DEFAULT.application},adminID:{get:()=>DEFAULT.adminID},root:{get:()=>DEFAULT.root}});const raw_user={get private(){if(!user_priv_raw)throw"User not logged in";return Crypto.AES.decrypt(user_priv_raw,aes_key)}};var user_id,user_public,user_private;const user=floDapps.user={get id(){if(!user_id)throw"User not logged in";return user_id},get public(){if(!user_public)throw"User not logged in";return user_public},get private(){if(user_private)return user_private instanceof Function?user_private():Crypto.AES.decrypt(user_private,aes_key);throw"User not logged in"},sign:message=>floCrypto.signData(message,raw_user.private),decrypt:data=>floCrypto.decryptData(data,raw_user.private),encipher:message=>Crypto.AES.encrypt(message,raw_user.private),decipher:data=>Crypto.AES.decrypt(data,raw_user.private),get db_name(){return"floDapps#"+floCrypto.toFloID(user.id)},lock(){user_private=user_priv_wrap},async unlock(){await user.private===raw_user.private&&(user_private=user_priv_raw)},get_contact(id){if(!user.contacts)throw"Contacts not available";if(user.contacts[id])return user.contacts[id];{let id_raw=floCrypto.decodeAddr(id).hex;for(let i in user.contacts)if(floCrypto.decodeAddr(i).hex==id_raw)return user.contacts[i]}},get_pubKey(id){if(!user.pubKeys)throw"Contacts not available";if(user.pubKeys[id])return user.pubKeys[id];{let id_raw=floCrypto.decodeAddr(id).hex;for(let i in user.pubKeys)if(floCrypto.decodeAddr(i).hex==id_raw)return user.pubKeys[i]}},clear(){user_id=user_public=user_private=void 0,user_priv_raw=aes_key=void 0,delete user.contacts,delete user.pubKeys,delete user.messages}};var subAdmins,trustedIDs,settings;function initIndexedDB(){return new Promise(((resolve,reject)=>{var obs_a={credentials:{},subAdmins:{},trustedIDs:{},settings:{},appObjects:{},generalData:{},lastVC:{}};initIndexedDB.appObs=initIndexedDB.appObs||{};for(let o in initIndexedDB.appObs)o in obs_a||(obs_a[o]=initIndexedDB.appObs[o]);Promise.all([compactIDB.initDB(DEFAULT.application,obs_a),compactIDB.initDB(DEFAULT.root,{lastTx:{},supernodes:{}})]).then((result=>{compactIDB.setDefaultDB(DEFAULT.application),resolve("IndexedDB App Storage Initated Successfully")})).catch((error=>reject(error)))}))}Object.defineProperties(window,{myFloID:{get:()=>{try{return user.id}catch{return}}},myUserID:{get:()=>{try{return user.id}catch{return}}},myPubKey:{get:()=>{try{return user.public}catch{return}}},myPrivKey:{get:()=>{try{return user.private}catch{return}}}}),Object.defineProperties(floGlobals,{subAdmins:{get:()=>subAdmins},trustedIDs:{get:()=>trustedIDs},settings:{get:()=>settings},contacts:{get:()=>user.contacts},pubKeys:{get:()=>user.pubKeys},messages:{get:()=>user.messages}});const startUpOptions={cloud:!0,app_config:!0};floDapps.startUpOptions={set app_config(val){!0!==val&&!1!==val||(startUpOptions.app_config=val)},get app_config(){return startUpOptions.app_config},set cloud(val){!0!==val&&!1!==val||(startUpOptions.cloud=val)},get cloud(){return startUpOptions.cloud}};const startUpFunctions=[];startUpFunctions.push((function(){return new Promise(((resolve,reject)=>{if(!startUpOptions.cloud)return resolve("No cloud for this app");const CLOUD_KEY="floCloudAPI#"+floCloudAPI.SNStorageID;compactIDB.readData("lastTx",CLOUD_KEY,DEFAULT.root).then((lastTx=>{var query_options={sentOnly:!0,pattern:floCloudAPI.SNStorageName};"number"==typeof lastTx?query_options.ignoreOld=lastTx:"string"==typeof lastTx&&(query_options.after=lastTx),floBlockchainAPI.readData(floCloudAPI.SNStorageID,query_options).then((result=>{compactIDB.readData("supernodes",CLOUD_KEY,DEFAULT.root).then((nodes=>{nodes=nodes||{};for(var i=result.data.length-1;i>=0;i--){var content=JSON.parse(result.data[i])[floCloudAPI.SNStorageName];for(let sn in content.removeNodes)delete nodes[sn];for(let sn in content.newNodes)nodes[sn]=content.newNodes[sn];for(let sn in content.updateNodes)sn in nodes&&(nodes[sn].uri=content.updateNodes[sn])}Promise.all([compactIDB.writeData("lastTx",result.lastItem,CLOUD_KEY,DEFAULT.root),compactIDB.writeData("supernodes",nodes,CLOUD_KEY,DEFAULT.root)]).then((_=>{floCloudAPI.init(nodes).then((result=>resolve("Loaded Supernode list\n"+result))).catch((error=>reject(error)))})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))})).catch((error=>reject(error)))}))})),startUpFunctions.push((function(){return new Promise(((resolve,reject)=>{if(!startUpOptions.app_config)return resolve("No configs for this app");compactIDB.readData("lastTx",`${DEFAULT.application}|${DEFAULT.adminID}`,DEFAULT.root).then((lastTx=>{var query_options={sentOnly:!0,pattern:DEFAULT.application};"number"==typeof lastTx?query_options.ignoreOld=lastTx:"string"==typeof lastTx&&(query_options.after=lastTx),floBlockchainAPI.readData(DEFAULT.adminID,query_options).then((result=>{for(var i=result.data.length-1;i>=0;i--){var content=JSON.parse(result.data[i])[DEFAULT.application];if(content&&"object"==typeof content){if(Array.isArray(content.removeSubAdmin))for(var j=0;j<content.removeSubAdmin.length;j++)compactIDB.removeData("subAdmins",content.removeSubAdmin[j]);if(Array.isArray(content.addSubAdmin))for(var k=0;k<content.addSubAdmin.length;k++)compactIDB.writeData("subAdmins",!0,content.addSubAdmin[k]);if(Array.isArray(content.removeTrustedID))for(j=0;j<content.removeTrustedID.length;j++)compactIDB.removeData("trustedIDs",content.removeTrustedID[j]);if(Array.isArray(content.addTrustedID))for(k=0;k<content.addTrustedID.length;k++)compactIDB.writeData("trustedIDs",!0,content.addTrustedID[k]);if(content.settings)for(let l in content.settings)compactIDB.writeData("settings",content.settings[l],l)}}compactIDB.writeData("lastTx",result.lastItem,`${DEFAULT.application}|${DEFAULT.adminID}`,DEFAULT.root),compactIDB.readAllData("subAdmins").then((result=>{subAdmins=Object.keys(result),compactIDB.readAllData("trustedIDs").then((result=>{trustedIDs=Object.keys(result),compactIDB.readAllData("settings").then((result=>{settings=result,resolve("Read app configuration from blockchain")}))}))}))}))})).catch((error=>reject(error)))}))})),startUpFunctions.push((function(){return new Promise(((resolve,reject)=>{if(!startUpOptions.cloud)return resolve("No cloud for this app");for(var loadData=["appObjects","generalData","lastVC"],promises=[],i=0;i<loadData.length;i++)promises[i]=compactIDB.readAllData(loadData[i]);Promise.all(promises).then((results=>{for(var i=0;i<loadData.length;i++)floGlobals[loadData[i]]=results[i];resolve("Loaded Data from app IDB")})).catch((error=>reject(error)))}))}));var keyInput=type=>new Promise(((resolve,reject)=>{let inputVal=prompt(`Enter ${type}: `);null===inputVal?reject(null):resolve(inputVal)}));function getCredentials(){const writeSharesToIDB=(shares,i=0,resultIndexes=[])=>new Promise((resolve=>{if(i>=shares.length)return resolve(resultIndexes);var n=floCrypto.randInt(0,1e5);compactIDB.addData("credentials",shares[i],n).then((res=>{resultIndexes.push(n),writeSharesToIDB(shares,i+1,resultIndexes).then((result=>resolve(result)))})).catch((error=>{writeSharesToIDB(shares,i,resultIndexes).then((result=>resolve(result)))}))})),getPrivateKeyCredentials=()=>new Promise(((resolve,reject)=>{var privKey,indexArr=localStorage.getItem(`${DEFAULT.application}#privKey`);indexArr?(indexArr=>new Promise(((resolve,reject)=>{for(var promises=[],i=0;i<indexArr.length;i++)promises.push(compactIDB.readData("credentials",indexArr[i]));Promise.all(promises).then((shares=>{var secret=floCrypto.retrieveShamirSecret(shares);secret?resolve(secret):reject("Shares are insufficient or incorrect")})).catch((error=>{clearCredentials(),location.reload()}))})))(JSON.parse(indexArr)).then((result=>resolve(result))).catch((error=>reject(error))):keyInput("PRIVATE_KEY").then((result=>{if(!result)return reject("Empty Private Key");var floID=floCrypto.getFloID(result);if(!floID||!floCrypto.validateFloID(floID))return reject("Invalid Private Key");privKey=result})).catch((error=>{console.log(error,"Generating Random Keys"),privKey=floCrypto.generateNewID().privKey})).finally((_=>{if(privKey){var threshold=floCrypto.randInt(10,20),shares=floCrypto.createShamirsSecretShares(privKey,threshold,threshold);writeSharesToIDB(shares).then((resultIndexes=>{localStorage.setItem(`${DEFAULT.application}#privKey`,JSON.stringify(resultIndexes));var randomPrivKey=floCrypto.generateNewID().privKey,randomThreshold=floCrypto.randInt(10,20),randomShares=floCrypto.createShamirsSecretShares(randomPrivKey,randomThreshold,randomThreshold);writeSharesToIDB(randomShares),resolve(privKey)}))}}))})),checkIfPinRequired=key=>new Promise(((resolve,reject)=>{52==key.length?resolve(key):keyInput("PIN/Password").then((pwd=>{try{let privKey=Crypto.AES.decrypt(key,pwd);resolve(privKey)}catch(error){reject("Access Denied: Incorrect PIN/Password")}})).catch((error=>reject("Access Denied: PIN/Password required")))}));return new Promise(((resolve,reject)=>{getPrivateKeyCredentials().then((key=>{checkIfPinRequired(key).then((privKey=>{try{user_public=floCrypto.getPubKeyHex(privKey),user_id=floCrypto.getAddress(privKey),startUpOptions.cloud&&floCloudAPI.user(user_id,privKey),user_priv_wrap=()=>checkIfPinRequired(key);let n=floCrypto.randInt(12,20);aes_key=floCrypto.randString(n),user_priv_raw=Crypto.AES.encrypt(privKey,aes_key),user_private=user_priv_wrap,resolve("Login Credentials loaded successful")}catch(error){console.log(error),reject("Corrupted Private Key")}})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}var startUpLog=(status,log)=>status?console.log(log):console.error(log);const callStartUpFunction=i=>new Promise(((resolve,reject)=>{startUpFunctions[i]().then((result=>{callStartUpFunction.completed+=1,startUpLog(!0,`${result}\nCompleted ${callStartUpFunction.completed}/${callStartUpFunction.total} Startup functions`),resolve(!0)})).catch((error=>{callStartUpFunction.failed+=1,startUpLog(!1,`${error}\nFailed ${callStartUpFunction.failed}/${callStartUpFunction.total} Startup functions`),reject(!1)}))}));var _midFunction;const callAndLog=p=>new Promise(((res,rej)=>{p.then((r=>{startUpLog(!0,r),res(r)})).catch((e=>{startUpLog(!1,e),rej(e)}))}));floDapps.launchStartUp=function(){return new Promise(((resolve,reject)=>{initIndexedDB().then((log=>{console.log(log),callStartUpFunction.total=startUpFunctions.length,callStartUpFunction.completed=0,callStartUpFunction.failed=0;let p1=new Promise(((res,rej)=>{Promise.all(startUpFunctions.map(((f,i)=>callStartUpFunction(i)))).then((r=>{callAndLog(new Promise(((res,rej)=>{_midFunction instanceof Function?_midFunction().then((r=>res("Mid startup function completed"))).catch((e=>rej("Mid startup function failed"))):res("No mid startup function")}))).then((r=>res(!0))).catch((e=>rej(!1)))}))})),p2=new Promise(((res,rej)=>{callAndLog(getCredentials()).then((r=>{callAndLog(new Promise(((resolve,reject)=>{compactIDB.initDB(user.db_name,{contacts:{},pubKeys:{},messages:{}}).then((result=>{resolve("UserDB Initated Successfully")})).catch((error=>reject("Init userDB failed")))}))).then((r=>{callAndLog(new Promise(((resolve,reject)=>{for(var loadData=["contacts","pubKeys","messages"],promises=[],i=0;i<loadData.length;i++)promises[i]=compactIDB.readAllData(loadData[i],user.db_name);Promise.all(promises).then((results=>{for(var i=0;i<loadData.length;i++)user[loadData[i]]=results[i];resolve("Loaded Data from userDB")})).catch((error=>reject("Load userDB failed")))}))).then((r=>res(!0))).catch((e=>rej(!1)))})).catch((e=>rej(!1)))})).catch((e=>rej(!1)))}));Promise.all([p1,p2]).then((r=>resolve("App Startup finished successful"))).catch((e=>reject("App Startup failed")))})).catch((error=>{startUpLog(!1,error),reject("App database initiation failed")}))}))},floDapps.addStartUpFunction=fn=>fn instanceof Function&&!startUpFunctions.includes(fn)&&startUpFunctions.push(fn),floDapps.setMidStartup=fn=>fn instanceof Function&&(_midFunction=fn),floDapps.setCustomStartupLogger=fn=>fn instanceof Function&&(startUpLog=fn),floDapps.setCustomPrivKeyInput=fn=>fn instanceof Function&&(keyInput=fn),floDapps.setAppObjectStores=appObs=>initIndexedDB.appObs=appObs,floDapps.storeContact=function(floID,name){return new Promise(((resolve,reject)=>{if(!floCrypto.validateAddr(floID))return reject("Invalid floID!");compactIDB.writeData("contacts",name,floID,user.db_name).then((result=>{user.contacts[floID]=name,resolve("Contact stored")})).catch((error=>reject(error)))}))},floDapps.storePubKey=function(floID,pubKey){return new Promise(((resolve,reject)=>floID in user.pubKeys?resolve("pubKey already stored"):floCrypto.validateAddr(floID)?floCrypto.verifyPubKey(pubKey,floID)?void compactIDB.writeData("pubKeys",pubKey,floID,user.db_name).then((result=>{user.pubKeys[floID]=pubKey,resolve("pubKey stored")})).catch((error=>reject(error))):reject("Incorrect pubKey"):reject("Invalid floID!")))},floDapps.sendMessage=function(floID,message){return new Promise(((resolve,reject)=>{let options={receiverID:floID,application:DEFAULT.root,comment:DEFAULT.application};floID in user.pubKeys&&(message=floCrypto.encryptData(JSON.stringify(message),user.pubKeys[floID])),floCloudAPI.sendApplicationData(message,"Message",options).then((result=>resolve(result))).catch((error=>reject(error)))}))},floDapps.requestInbox=function(callback){return new Promise(((resolve,reject)=>{let lastVC=Object.keys(user.messages).sort().pop(),options={receiverID:user.id,application:DEFAULT.root,lowerVectorClock:lastVC+1},privKey=raw_user.private;options.callback=(d,e)=>{for(let v in d){try{d[v].message instanceof Object&&"secret"in d[v].message&&(d[v].message=floCrypto.decryptData(d[v].message,privKey))}catch(error){}compactIDB.writeData("messages",d[v],v,user.db_name),user.messages[v]=d[v]}callback instanceof Function&&callback(d,e)},floCloudAPI.requestApplicationData("Message",options).then((result=>resolve(result))).catch((error=>reject(error)))}))},floDapps.manageAppConfig=function(adminPrivKey,addList,rmList,settings){return new Promise(((resolve,reject)=>{if(!startUpOptions.app_config)return reject("No configs for this app");if(Array.isArray(addList)&&addList.length||(addList=void 0),Array.isArray(rmList)&&rmList.length||(rmList=void 0),settings&&"object"==typeof settings&&Object.keys(settings).length||(settings=void 0),!addList&&!rmList&&!settings)return reject("No configuration change");var floData={[DEFAULT.application]:{addSubAdmin:addList,removeSubAdmin:rmList,settings:settings}},floID=floCrypto.getFloID(adminPrivKey);floID!=DEFAULT.adminID?reject("Access Denied for Admin privilege"):floBlockchainAPI.writeData(floID,JSON.stringify(floData),adminPrivKey).then((result=>resolve(["Updated App Configuration",result]))).catch((error=>reject(error)))}))},floDapps.manageAppTrustedIDs=function(adminPrivKey,addList,rmList){return new Promise(((resolve,reject)=>{if(!startUpOptions.app_config)return reject("No configs for this app");if(Array.isArray(addList)&&addList.length||(addList=void 0),Array.isArray(rmList)&&rmList.length||(rmList=void 0),!addList&&!rmList)return reject("No change in list");var floData={[DEFAULT.application]:{addTrustedID:addList,removeTrustedID:rmList}},floID=floCrypto.getFloID(adminPrivKey);floID!=DEFAULT.adminID?reject("Access Denied for Admin privilege"):floBlockchainAPI.writeData(floID,JSON.stringify(floData),adminPrivKey).then((result=>resolve(["Updated App Configuration",result]))).catch((error=>reject(error)))}))};const clearCredentials=floDapps.clearCredentials=function(){return new Promise(((resolve,reject)=>{compactIDB.clearData("credentials",DEFAULT.application).then((result=>{localStorage.removeItem(`${DEFAULT.application}#privKey`),user.clear(),resolve("privKey credentials deleted!")})).catch((error=>reject(error)))}))};floDapps.deleteUserData=function(credentials=!1){return new Promise(((resolve,reject)=>{let p=[];p.push(compactIDB.deleteDB(user.db_name)),credentials&&p.push(clearCredentials()),Promise.all(p).then((result=>resolve("User database(local) deleted"))).catch((error=>reject(error)))}))},floDapps.deleteAppData=function(){return new Promise(((resolve,reject)=>{compactIDB.deleteDB(DEFAULT.application).then((result=>{localStorage.removeItem(`${DEFAULT.application}#privKey`),user.clear(),compactIDB.removeData("lastTx",`${DEFAULT.application}|${DEFAULT.adminID}`,DEFAULT.root).then((result=>resolve("App database(local) deleted"))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},floDapps.securePrivKey=function(pwd){return new Promise((async(resolve,reject)=>{let indexArr=localStorage.getItem(`${DEFAULT.application}#privKey`);if(!indexArr)return reject("PrivKey not found");indexArr=JSON.parse(indexArr);let encryptedKey=Crypto.AES.encrypt(await user.private,pwd),threshold=indexArr.length,shares=floCrypto.createShamirsSecretShares(encryptedKey,threshold,threshold),promises=[];for(var i=0;i<threshold;i++)promises.push((share=shares[i],index=indexArr[i],compactIDB.writeData("credentials",share,index,DEFAULT.application)));var share,index;Promise.all(promises).then((results=>resolve("Private Key Secured"))).catch((error=>reject(error)))}))},floDapps.verifyPin=function(pin=null){return new Promise(((resolve,reject)=>{var indexArr=localStorage.getItem(`${DEFAULT.application}#privKey`);console.info(indexArr),indexArr||reject("No login credentials found"),function(indexArr){return new Promise(((resolve,reject)=>{for(var promises=[],i=0;i<indexArr.length;i++)promises.push(compactIDB.readData("credentials",indexArr[i]));Promise.all(promises).then((shares=>{var secret=floCrypto.retrieveShamirSecret(shares);console.info(shares,secret),secret?resolve(secret):reject("Shares are insufficient or incorrect")})).catch((error=>{clearCredentials(),location.reload()}))}))}(JSON.parse(indexArr)).then((key=>{if(52==key.length)null===pin?resolve("Private key not secured"):reject("Private key not secured");else{if(null===pin)return reject("PIN/Password required");try{Crypto.AES.decrypt(key,pin);resolve("PIN/Password verified")}catch(error){reject("Incorrect PIN/Password")}}})).catch((error=>reject(error)))}))};const getNextGeneralData=floDapps.getNextGeneralData=function(type,vectorClock=null,options={}){var fk=floCloudAPI.util.filterKey(type,options);vectorClock=vectorClock||getNextGeneralData[fk]||"0";var filteredResult={};if(floGlobals.generalData[fk])for(let d in floGlobals.generalData[fk])d>vectorClock&&(filteredResult[d]=JSON.parse(JSON.stringify(floGlobals.generalData[fk][d])));else if(options.comment){let comment=options.comment;delete options.comment;let fk=floCloudAPI.util.filterKey(type,options);for(let d in floGlobals.generalData[fk])d>vectorClock&&floGlobals.generalData[fk][d].comment==comment&&(filteredResult[d]=JSON.parse(JSON.stringify(floGlobals.generalData[fk][d])))}if(options.decrypt){let decryptionKey=!0===options.decrypt?raw_user.private:options.decrypt;Array.isArray(decryptionKey)||(decryptionKey=[decryptionKey]);for(let f in filteredResult){let data=filteredResult[f];try{if(data.message instanceof Object&&"secret"in data.message)for(let key of decryptionKey)try{let tmp=floCrypto.decryptData(data.message,key);data.message=JSON.parse(tmp);break}catch(error){}}catch(error){}}}return getNextGeneralData[fk]=Object.keys(filteredResult).sort().pop(),filteredResult},syncData=floDapps.syncData={};syncData.oldDevice=()=>new Promise(((resolve,reject)=>{let sync={contacts:user.contacts,pubKeys:user.pubKeys,messages:user.messages},message=Crypto.AES.encrypt(JSON.stringify(sync),raw_user.private),options={receiverID:user.id,application:DEFAULT.root};floCloudAPI.sendApplicationData(message,"syncData",options).then((result=>resolve(result))).catch((error=>reject(error)))})),syncData.newDevice=()=>new Promise(((resolve,reject)=>{var options={receiverID:user.id,senderID:user.id,application:DEFAULT.root,mostRecent:!0};floCloudAPI.requestApplicationData("syncData",options).then((response=>{let vc=Object.keys(response).sort().pop(),sync=JSON.parse(Crypto.AES.decrypt(response[vc].message,raw_user.private)),promises=[];["contacts","pubKeys","messages"].forEach((c=>{for(let i in sync[c])key=i,val=sync[c][i],obs=c,promises.push(compactIDB.writeData(obs,val,key,user.db_name)),user[c][i]=sync[c][i];var key,val,obs})),Promise.all(promises).then((results=>resolve("Sync data successful"))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}();