!function(){const rmMessenger=window.rmMessenger={},user={get id(){return floDapps.user.id},get public(){return floDapps.user.public}},expiredKeys={},UI={group:(d,e)=>console.log(d,e),pipeline:(d,e)=>console.log(d,e),direct:(d,e)=>console.log(d,e),chats:c=>console.log(c),mails:m=>console.log(m),marked:r=>console.log(r)};rmMessenger.renderUI={},Object.defineProperties(rmMessenger.renderUI,{chats:{set:ui_fn=>UI.chats=ui_fn},directChat:{set:ui_fn=>UI.direct=ui_fn},groupChat:{set:ui_fn=>UI.group=ui_fn},pipeline:{set:ui_fn=>UI.pipeline=ui_fn},mails:{set:ui_fn=>UI.mails=ui_fn},marked:{set:ui_fn=>UI.marked=ui_fn}});const _loaded={};Object.defineProperties(rmMessenger,{chats:{get:()=>_loaded.chats},groups:{get:()=>_loaded.groups},pipeline:{get:()=>_loaded.pipeline},blocked:{get:()=>_loaded.blocked},marked:{get:()=>_loaded.marked}});var directConnID=[],groupConnID={},pipeConnID={};function sendRaw(message,recipient,type,encrypt=null,comment=void 0){return new Promise(((resolve,reject)=>{if(!floCrypto.validateAddr(recipient))return reject("Invalid Recipient");if([!0,null].includes(encrypt)){let r_pubKey=floDapps.user.get_pubKey(recipient);if(r_pubKey)message=floCrypto.encryptData(message,r_pubKey);else if(!0===encrypt)return reject("recipient's pubKey not found")}let options={receiverID:recipient};comment&&(options.comment=comment),floCloudAPI.sendApplicationData(message,type,options).then((result=>resolve(result))).catch((error=>reject(error)))}))}function encrypt(value,key=_loaded.appendix.AESKey){return Crypto.AES.encrypt(value,key)}function decrypt(value,key=_loaded.appendix.AESKey){return Crypto.AES.decrypt(value,key)}function addMark(key,mark){return new Promise(((resolve,reject)=>{compactIDB.readData("marked",key).then((result=>{if(result){if(result.includes(mark))return resolve("Mark already exist");result.push(mark)}else result=[mark];compactIDB.writeData("marked",result,key).then((result=>resolve(result))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}rmMessenger.conn={},Object.defineProperties(rmMessenger.conn,{direct:{get:()=>directConnID},group:{get:()=>Object.assign({},groupConnID)}}),rmMessenger.sendRaw=sendRaw,rmMessenger.encrypt=encrypt;function listRequests(obs,options=null){return new Promise(((resolve,reject)=>{compactIDB.readAllData(obs).then((result=>{if(!options||"object"!=typeof options)return resolve(result);let filtered={};for(let k in result){let val=result[k];options.type&&options.type==val.type||(options.floID&&options.floID==val.floID||void 0!==options.completed&&options.completed==!val.completed||(filtered[k]=val))}resolve(filtered)})).catch((error=>reject(error)))}))}rmMessenger.blockUser=function(floID){return new Promise(((resolve,reject)=>{if(_loaded.blocked.has(floID))return resolve("User is already blocked");compactIDB.addData("blocked",!0,floID).then((result=>{_loaded.blocked.add(floID),resolve("Blocked User: "+floID)})).catch((error=>reject(error)))}))},rmMessenger.unblockUser=function(floID){return new Promise(((resolve,reject)=>{if(!_loaded.blocked.has(floID))return resolve("User is not blocked");compactIDB.removeData("blocked",floID).then((result=>{_loaded.blocked.delete(floID),resolve("Unblocked User: "+floID)})).catch((error=>reject(error)))}))},rmMessenger.sendMessage=function(message,receiver){return new Promise(((resolve,reject)=>{sendRaw(message,receiver,"MESSAGE").then((result=>{let vc=result.vectorClock,data={floID:receiver,time:result.time,category:"sent",message:encrypt(message)};_loaded.chats[receiver]=parseInt(vc),compactIDB.writeData("chats",parseInt(vc),receiver),compactIDB.addData("messages",Object.assign({},data),`${receiver}|${vc}`),data.message=message,resolve({[vc]:data})})).catch((error=>reject(error)))}))},rmMessenger.sendMail=function(subject,content,recipients,prev=null){return new Promise(((resolve,reject)=>{Array.isArray(recipients)||(recipients=[recipients]);let mail={subject:subject,content:content,ref:Date.now()+floCrypto.randString(8,!0),prev:prev},promises=recipients.map((r=>sendRaw(JSON.stringify(mail),r,"MAIL")));Promise.allSettled(promises).then((results=>{if(mail.time=Date.now(),mail.from=user.id,mail.to=[],results.forEach((r=>{"fulfilled"===r.status&&mail.to.push(r.value.receiverID)})),0===mail.to.length)return reject(results);mail.content=encrypt(content),compactIDB.addData("mails",Object.assign({},mail),mail.ref),mail.content=content,resolve({[mail.ref]:mail})}))}))},rmMessenger.list_request_sent=(options=null)=>listRequests("request_sent",options),rmMessenger.list_request_received=(options=null)=>listRequests("request_received",options),rmMessenger.list_response_sent=(options=null)=>listRequests("response_sent",options),rmMessenger.list_response_received=(options=null)=>listRequests("response_received",options),rmMessenger.request_pubKey=(receiver,message="")=>function(receiver,type,message,encrypt=null){return new Promise(((resolve,reject)=>{sendRaw(message,receiver,"REQUEST",encrypt,type).then((result=>{let vc=result.vectorClock,data={floID:receiver,time:result.time,message:message,type:type};compactIDB.addData("request_sent",data,vc),resolve({[vc]:data})})).catch((error=>reject(error)))}))}(receiver,"PUBLIC_KEY",message,!1),rmMessenger.respond_pubKey=(req_id,message="")=>function(req_id,message,encrypt=null){return new Promise(((resolve,reject)=>{compactIDB.readData("request_received",req_id).then((request=>{sendRaw(JSON.stringify({value:message,reqID:req_id}),request.floID,"RESPONSE",encrypt,request.type).then((result=>{let vc=result.vectorClock,data={floID:request.floID,time:result.time,message:message,type:request.type,reqID:req_id};compactIDB.addData("response_sent",data,vc),request.completed=vc,compactIDB.writeData("request_received",request,req_id),resolve({[vc]:data})})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}(req_id,message,!1);const processData={};processData.direct=function(){return(unparsed,newInbox)=>{if(floDapps.storePubKey(unparsed.senderID,unparsed.pubKey),_loaded.blocked.has(unparsed.senderID)&&"REVOKE_KEY"!==unparsed.type)throw"blocked-user";unparsed.message instanceof Object&&"secret"in unparsed.message&&(unparsed.message=floDapps.user.decrypt(unparsed.message));let vc=unparsed.vectorClock;switch(unparsed.type){case"MESSAGE":{let dm={time:unparsed.time,floID:unparsed.senderID,category:"received",message:encrypt(unparsed.message)};console.debug(dm,`${dm.floID}|${vc}`),compactIDB.addData("messages",Object.assign({},dm),`${dm.floID}|${vc}`),_loaded.chats[dm.floID]=parseInt(vc),compactIDB.writeData("chats",parseInt(vc),dm.floID),dm.message=unparsed.message,newInbox.messages[vc]=dm,addMark(dm.floID,"unread");break}case"REQUEST":{let req={floID:unparsed.senderID,time:unparsed.time,message:unparsed.message,type:unparsed.comment};compactIDB.addData("request_received",req,vc),newInbox.requests[vc]=req;break}case"RESPONSE":{let data=JSON.parse(unparsed.message),res={floID:unparsed.senderID,time:unparsed.time,message:data.value,type:unparsed.comment,reqID:data.reqID};compactIDB.addData("response_received",res,vc),compactIDB.readData("request_sent",data.reqID).then((req=>{req.completed=vc,compactIDB.writeData("request_sent",req,data.reqID)})),newInbox.responses[vc]=res;break}case"MAIL":{let data=JSON.parse(unparsed.message),mail={time:unparsed.time,from:unparsed.senderID,to:[unparsed.receiverID],subject:data.subject,content:encrypt(data.content),ref:data.ref,prev:data.prev};compactIDB.addData("mails",Object.assign({},mail),mail.ref),mail.content=data.content,newInbox.mails[mail.ref]=mail,addMark(mail.ref,"unread");break}case"CREATE_GROUP":{let groupInfo=JSON.parse(unparsed.message),h=["groupID","created","admin"].map((x=>groupInfo[x])).join("|");if(groupInfo.admin===unparsed.senderID&&floCrypto.verifySign(h,groupInfo.hash,groupInfo.pubKey)&&floCrypto.getFloID(groupInfo.pubKey)===groupInfo.groupID){let eKey=groupInfo.eKey;groupInfo.eKey=encrypt(eKey),compactIDB.writeData("groups",Object.assign({},groupInfo),groupInfo.groupID),groupInfo.eKey=eKey,_loaded.groups[groupInfo.groupID]=groupInfo,requestGroupInbox(groupInfo.groupID),newInbox.newgroups.push(groupInfo.groupID)}break}case"REVOKE_KEY":{let r=JSON.parse(unparsed.message),groupInfo=_loaded.groups[r.groupID];if(unparsed.senderID===groupInfo.admin){"object"!=typeof expiredKeys[r.groupID]&&(expiredKeys[r.groupID]={}),expiredKeys[r.groupID][vc]=groupInfo.eKey;let eKey=r.newKey;groupInfo.eKey=encrypt(eKey),compactIDB.writeData("groups",Object.assign({},groupInfo),groupInfo.groupID),groupInfo.eKey=eKey,newInbox.keyrevoke.push(groupInfo.groupID)}break}case"CREATE_PIPELINE":{let pipelineInfo=JSON.parse(unparsed.message),eKey=pipelineInfo.eKey;pipelineInfo.eKey=encrypt(eKey),compactIDB.addData("pipeline",Object.assign({},pipelineInfo),pipelineInfo.id),pipelineInfo.eKey=eKey,_loaded.pipeline[pipelineInfo.id]=pipelineInfo,requestPipelineInbox(pipelineInfo.id,pipelineInfo.model),newInbox.pipeline[pipelineInfo.id]=pipelineInfo.model}}}},rmMessenger.getMail=function(mailRef){return new Promise(((resolve,reject)=>{compactIDB.readData("mails",mailRef).then((mail=>{mail.content=decrypt(mail.content),resolve(mail)})).catch((error=>reject(error)))}))};const getChatOrder=rmMessenger.getChatOrder=function(separate=!1){let result;return separate?(result={},result.direct=Object.keys(_loaded.chats).map((a=>[_loaded.chats[a],a])).sort(((a,b)=>b[0]-a[0])).map((a=>a[1])),result.group=Object.keys(_loaded.groups).map((a=>[parseInt(_loaded.appendix[`lastReceived_${a}`]),a])).sort(((a,b)=>b[0]-a[0])).map((a=>a[1])),result.pipeline=Object.keys(_loaded.pipeline).map((a=>[parseInt(_loaded.appendix[`lastReceived_${a}`]),a])).sort(((a,b)=>b[0]-a[0])).map((a=>a[1]))):result=Object.keys(_loaded.chats).map((a=>[_loaded.chats[a],a])).concat(Object.keys(_loaded.groups).map((a=>[parseInt(_loaded.appendix[`lastReceived_${a}`]),a]))).concat(Object.keys(_loaded.pipeline).map((a=>[parseInt(_loaded.appendix[`lastReceived_${a}`]),a]))).sort(((a,b)=>b[0]-a[0])).map((a=>a[1])),result};rmMessenger.storeContact=function(floID,name){return floDapps.storeContact(floID,name)};const loadDataFromIDB=function(defaultList=!0){return new Promise(((resolve,reject)=>{dataList=defaultList?["mails","marked","groups","pipeline","chats","blocked","appendix"]:["messages","mails","marked","chats","groups","gkeys","pipeline","blocked","appendix"];let promises=[];for(var i=0;i{let data={};for(var i=0;i{data.appendix.AESKey=AESKey,resolve(data)})).catch((error=>reject("Unable to Generate AES Key")))}})).catch((error=>reject(error)))}))};rmMessenger.addMark=function(key,mark){return _loaded.marked.hasOwnProperty(key)&&!_loaded.marked[key].includes(mark)&&_loaded.marked[key].push(mark),addMark(key,mark)},rmMessenger.removeMark=function(key,mark){return _loaded.marked.hasOwnProperty(key)&&(_loaded.marked[key]=_loaded.marked[key].filter((v=>v!==mark))),function(key,mark){return new Promise(((resolve,reject)=>{compactIDB.readData("marked",key).then((result=>{if(!result||!result.includes(mark))return resolve("Mark doesnot exist");result.splice(result.indexOf(mark),1),compactIDB.writeData("marked",result,key).then((result=>resolve("Mark removed"))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}(key,mark)},rmMessenger.addChat=function(chatID){return new Promise(((resolve,reject)=>{compactIDB.addData("chats",0,chatID).then((result=>resolve("Added chat"))).catch((error=>reject(error)))}))},rmMessenger.rmChat=function(chatID){return new Promise(((resolve,reject)=>{compactIDB.removeData("chats",chatID).then((result=>resolve("Chat removed"))).catch((error=>reject(error)))}))},rmMessenger.clearChat=function(chatID){return new Promise(((resolve,reject)=>{let options={lowerKey:`${chatID}|`,upperKey:`${chatID}||`};compactIDB.searchData("messages",options).then((result=>{let promises=[];for(let i in result)promises.push(compactIDB.removeData("messages",i));Promise.all(promises).then((result=>resolve("Chat cleared"))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))};const getChat=rmMessenger.getChat=function(chatID){return new Promise(((resolve,reject)=>{let options={lowerKey:`${chatID}|`,upperKey:`${chatID}||`};compactIDB.searchData("messages",options).then((result=>{for(let i in result)result[i].message&&(result[i].message=decrypt(result[i].message));resolve(result)})).catch((error=>reject(error)))}))};rmMessenger.backupData=function(){return new Promise(((resolve,reject)=>{loadDataFromIDB(!1).then((data=>{delete data.appendix.AESKey,data.contacts=floGlobals.contacts,data.pubKeys=floGlobals.pubKeys,data=btoa(unescape(encodeURIComponent(JSON.stringify(data))));let blobData={floID:user.id,pubKey:user.public,data:floDapps.user.encipher(data)};blobData.sign=floDapps.user.sign(blobData.data),resolve(new Blob([JSON.stringify(blobData)],{type:"application/json"}))})).catch((error=>reject(error)))}))};const parseBackup=rmMessenger.parseBackup=function(blob){return new Promise(((resolve,reject)=>{if(blob instanceof Blob||blob instanceof File){let reader=new FileReader;reader.onload=evt=>{var blobData=JSON.parse(evt.target.result);if(floCrypto.verifySign(blobData.data,blobData.sign,blobData.pubKey))if(user.id!==blobData.floID||user.public!==blobData.pubKey)reject("Invalid Backup file: Incorrect floID");else try{let data=floDapps.user.decipher(blobData.data);try{data=JSON.parse(decodeURIComponent(escape(atob(data)))),resolve(data)}catch(e){reject("Corrupted Backup file: Parse failed")}}catch(e){reject("Corrupted Backup file: Decryption failed")}else reject("Corrupted Backup file: Signature verification failed")},reader.readAsText(blob)}else reject("Backup is not a valid File (or) Blob")}))};rmMessenger.restoreData=function(arg){return new Promise(((resolve,reject)=>{if(arg instanceof Blob||arg instanceof File)var parseData=parseBackup;else parseData=data=>new Promise(((res,rej)=>res(data)));parseData(arg).then((data=>{for(let m in data.messages)data.messages[m].message&&(data.messages[m].message=encrypt(data.messages[m].message));for(let m in data.mails)data.mails[m].content=encrypt(data.mails[m].content);for(let k in data.gkeys)data.gkeys[k]=encrypt(data.gkeys[k]);for(let g in data.groups)data.groups[g].eKey=encrypt(data.groups[g].eKey);for(let p in data.pipeline)data.pipeline[p].eKey=encrypt(data.pipeline[p].eKey);for(let c in data.chats)data.chats[c]<=_loaded.chats[c]&&delete data.chats[c];for(let l in data.appendix)l.startsWith("lastReceived")&&data.appendix[l]<=_loaded.appendix[l]&&delete data.appendix[l];for(let c in data.contacts)c in floGlobals.contacts&&delete data.contacts[c];for(let p in data.pubKeys)p in floGlobals.pubKeys&&delete data.pubKeys[p];let promises=[];for(let obs in data){let writeFn;switch(obs){case"contacts":writeFn=(k,v)=>floDapps.storeContact(k,v);break;case"pubKeys":writeFn=(k,v)=>floDapps.storePubKey(k,v);break;default:writeFn=(k,v)=>compactIDB.writeData(obs,v,k)}for(let k in data[obs])promises.push(writeFn(k,data[obs][k]))}Promise.all(promises).then((results=>resolve("Restore Successful"))).catch((error=>reject("Restore Failed: Unable to write to IDB")))})).catch((error=>reject(error)))}))},rmMessenger.clearUserData=function(){return new Promise(((resolve,reject)=>{let user_floID=floCrypto.toFloID(user.id),promises=[compactIDB.deleteDB(`${floGlobals.application}_${user_floID}`),compactIDB.removeData("lastTx",`${floGlobals.application}|${user_floID}`,floDapps.root),floDapps.clearCredentials()];Promise.all(promises).then((result=>resolve("User Data cleared"))).catch((error=>reject(error)))}))},rmMessenger.createGroup=function(groupname,description=""){return new Promise(((resolve,reject)=>{if(!groupname)return reject("Invalid Group Name");let id=floCrypto.generateNewID(),groupInfo={groupID:id.floID,pubKey:id.pubKey,admin:user.id,name:groupname,description:description,created:Date.now(),members:[user.id]},h=["groupID","created","admin"].map((x=>groupInfo[x])).join("|");groupInfo.hash=floCrypto.signData(h,id.privKey);let eKey=floCrypto.randString(16,!1);groupInfo.eKey=encrypt(eKey);let p1=compactIDB.addData("groups",groupInfo,id.floID),p2=compactIDB.addData("gkeys",encrypt(id.privKey),id.floID);Promise.all([p1,p2]).then((r=>{groupInfo.eKey=eKey,_loaded.groups[id.floID]=groupInfo,requestGroupInbox(id.floID),resolve(groupInfo)})).catch((e=>reject(e)))}))},rmMessenger.changeGroupName=function(groupID,name){return new Promise(((resolve,reject)=>{let groupInfo=_loaded.groups[groupID];if(user.id!==groupInfo.admin)return reject("Access denied: Admin only!");sendRaw(encrypt(name,groupInfo.eKey),groupID,"UP_NAME",!1).then((result=>resolve("Name updated"))).catch((error=>reject(error)))}))},rmMessenger.changeGroupDescription=function(groupID,description){return new Promise(((resolve,reject)=>{let groupInfo=_loaded.groups[groupID];if(user.id!==groupInfo.admin)return reject("Access denied: Admin only!");sendRaw(encrypt(description,groupInfo.eKey),groupID,"UP_DESCRIPTION",!1).then((result=>resolve("Description updated"))).catch((error=>reject(error)))}))},rmMessenger.addGroupMembers=function(groupID,newMem,note=void 0){return new Promise(((resolve,reject)=>{Array.isArray(newMem)||"string"!=typeof newMem||(newMem=[newMem]);let imem1=[],imem2=[];if(newMem.forEach((m=>floCrypto.validateAddr(m)?m in floGlobals.pubKeys?null:imem2.push(m):imem1.push(m))),imem1.length)return reject(`Invalid Members(floIDs): ${imem1}`);if(imem2.length)return reject(`Invalid Members (pubKey not available): ${imem2}`);let groupInfo=_loaded.groups[groupID];if(user.id!==groupInfo.admin)return reject("Access denied: Admin only!");let k=groupInfo.eKey;groupInfo=JSON.stringify(groupInfo);let promises=newMem.map((m=>sendRaw(groupInfo,m,"CREATE_GROUP",!0)));Promise.allSettled(promises).then((results=>{let success=[],failed=[];for(let i in results)"fulfilled"===results[i].status?success.push(newMem[i]):"rejected"===results[i].status&&failed.push(newMem[i]);sendRaw(encrypt(success.join("|"),k),groupID,"ADD_MEMBERS",!1,note).then((r=>resolve(`Members added: ${success}`))).catch((e=>reject(e)))}))}))},rmMessenger.rmGroupMembers=function(groupID,rmMem,note=void 0){return new Promise(((resolve,reject)=>{Array.isArray(rmMem)||"string"!=typeof rmMem||(rmMem=[rmMem]);let groupInfo=_loaded.groups[groupID],imem=rmMem.filter((m=>!groupInfo.members.includes(m)));if(imem.length)return reject(`Invalid members: ${imem}`);if(user.id!==groupInfo.admin)return reject("Access denied: Admin only!");let p1=sendRaw(encrypt(rmMem.join("|"),groupInfo.eKey),groupID,"RM_MEMBERS",!1,note);groupInfo.members=groupInfo.members.filter((m=>!rmMem.includes(m)));let p2=revokeKey(groupID);Promise.all([p1,p2]).then((r=>resolve(`Members removed: ${rmMem}`))).catch((e=>reject(e)))}))};const revokeKey=rmMessenger.revokeKey=function(groupID){return new Promise(((resolve,reject)=>{let groupInfo=_loaded.groups[groupID];if(user.id!==groupInfo.admin)return reject("Access denied: Admin only!");let newKey=floCrypto.randString(16,!1);Promise.all(groupInfo.members.map((m=>sendRaw(JSON.stringify({newKey:newKey,groupID:groupID}),m,"REVOKE_KEY",!0)))).then((result=>{resolve("Group key revoked")})).catch((error=>reject(error)))}))};rmMessenger.sendGroupMessage=function(message,groupID){return new Promise(((resolve,reject)=>{let k=_loaded.groups[groupID].eKey;sendRaw(message=encrypt(message,k),groupID,"GROUP_MSG",!1).then((result=>resolve(`${groupID}: ${message}`))).catch((error=>reject(error)))}))};const disableGroup=rmMessenger.disableGroup=function(groupID){return new Promise(((resolve,reject)=>{if(!_loaded.groups[groupID])return reject("Group not found");let groupInfo=Object.assign({},_loaded.groups[groupID]);if(groupInfo.disabled)return resolve("Group already diabled");groupInfo.disabled=!0,groupInfo.eKey=encrypt(groupInfo.eKey),compactIDB.writeData("groups",groupInfo,groupID).then((result=>{floCloudAPI.closeRequest(groupConnID[groupID]),delete groupConnID[groupID],resolve("Group diabled")})).catch((error=>reject(error)))}))};function requestGroupInbox(groupID,_async=!0){groupConnID[groupID]&&(floCloudAPI.closeRequest(groupConnID[groupID]),delete groupConnID[groupID]);const parseData=processData.group(groupID);let fn=floCloudAPI.requestApplicationData(null,{receiverID:groupID,lowerVectorClock:_loaded.appendix[`lastReceived_${groupID}`]+1,callback:function(dataSet,error){if(error)return console.error(error);console.info(dataSet);let newInbox={messages:{}},infoChange=!1;for(let vc in dataSet)if(groupID===dataSet[vc].receiverID)try{infoChange=parseData(dataSet[vc],newInbox)||infoChange,(!_loaded.appendix[`lastReceived_${groupID}`]||_loaded.appendix[`lastReceived_${groupID}`]{fn.then((conn_id=>{groupConnID[groupID]=conn_id,resolve(`Connected to group ${groupID}`)})).catch((error=>reject(error)))}));fn.then((conn_id=>groupConnID[groupID]=conn_id)).catch((error=>console.error(`request-group(${groupID}):`,error)))}processData.group=function(groupID){return(unparsed,newInbox)=>{if(!_loaded.groups[groupID].members.includes(unparsed.senderID))return;floDapps.storePubKey(unparsed.senderID,unparsed.pubKey);let data={time:unparsed.time,sender:unparsed.senderID,groupID:unparsed.receiverID},vc=unparsed.vectorClock,k=_loaded.groups[groupID].eKey;if(expiredKeys[groupID]){for(var ex=Object.keys(expiredKeys[groupID]).sort();ex.length&&vc>ex[0];)ex.shift();ex.length&&(k=expiredKeys[groupID][ex.shift()])}unparsed.message=decrypt(unparsed.message,k);var infoChange=!1;if("GROUP_MSG"===unparsed.type)data.message=encrypt(unparsed.message);else if(data.sender===_loaded.groups[groupID].admin){let groupInfo=_loaded.groups[groupID];switch(data.admin=!0,unparsed.type){case"ADD_MEMBERS":data.newMembers=unparsed.message.split("|"),data.note=unparsed.comment,groupInfo.members=Array.from(new Set(groupInfo.members.concat(data.newMembers)));break;case"UP_DESCRIPTION":data.description=unparsed.message,groupInfo.description=data.description;break;case"RM_MEMBERS":if(data.rmMembers=unparsed.message.split("|"),data.note=unparsed.comment,groupInfo.members=groupInfo.members.filter((m=>!data.rmMembers.includes(m))),data.rmMembers.includes(user.id))return void disableGroup(groupID);break;case"UP_NAME":data.name=unparsed.message,groupInfo.name=data.name}infoChange=!0}return compactIDB.addData("messages",Object.assign({},data),`${groupID}|${vc}`),data.message&&(data.message=decrypt(data.message)),newInbox.messages[vc]=data,floCrypto.isSameAddr(data.sender,user.id)||addMark(data.groupID,"unread"),infoChange}},rmMessenger.init=function(){return new Promise(((resolve,reject)=>{new Promise(((resolve,reject)=>{let user_db=`${floGlobals.application}_${floCrypto.toFloID(user.id)}`;compactIDB.initDB(user_db,{messages:{},mails:{},marked:{},chats:{},groups:{},gkeys:{},blocked:{},pipeline:{},request_sent:{},request_received:{},response_sent:{},response_received:{},flodata:{},appendix:{},userSettings:{},multisigLabels:{}}).then((result=>{console.info(result),compactIDB.setDefaultDB(user_db),resolve("Messenger UserDB Initated Successfully")})).catch((error=>reject(error)))})).then((result=>{console.debug(result),loadDataFromIDB().then((data=>{console.debug(data),_loaded.appendix=data.appendix,_loaded.groups=data.groups,_loaded.pipeline=data.pipeline,_loaded.chats=data.chats,_loaded.marked=data.marked,_loaded.blocked=new Set(Object.keys(data.blocked)),UI.chats(getChatOrder()),UI.mails(data.mails),UI.marked(data.marked),resolve("Loaded local data");let promises=[];promises.push(function(){directConnID.length&&(directConnID.forEach((id=>floCloudAPI.closeRequest(id))),directConnID=[]);const parseData=processData.direct();let callbackFn=function(dataSet,error){if(error)return console.error(error);let newInbox={messages:{},requests:{},responses:{},mails:{},newgroups:[],keyrevoke:[],pipeline:{}};for(let vc in dataSet)try{parseData(dataSet[vc],newInbox)}catch(error){console.log(error)}finally{_loaded.appendix.lastReceived{const promises=[floCloudAPI.requestApplicationData(null,{receiverID:user.id,lowerVectorClock:_loaded.appendix.lastReceived+1,callback:callbackFn}),floCloudAPI.requestApplicationData(null,{receiverID:floEthereum.ethAddressFromCompressedPublicKey(user.public),lowerVectorClock:_loaded.appendix.lastReceived+1,callback:callbackFn})];Promise.all(promises).then((connectionIds=>{directConnID=[...directConnID,...connectionIds],resolve("Direct Inbox connected")})).catch((error=>reject(error)))}))}());for(let g in data.groups)!0!==data.groups[g].disabled&&promises.push(requestGroupInbox(g,!1));for(let p in data.pipeline)!0!==data.pipeline[p].disabled&&promises.push(requestPipelineInbox(p,data.pipeline[p].model,!1));loadDataFromBlockchain().then((result=>{Promise.all(promises).then((result=>resolve("Messenger initiated"))).catch((error=>reject(error)))})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}))};const loadDataFromBlockchain=rmMessenger.loadDataFromBlockchain=function(){return new Promise(((resolve,reject)=>{let user_floID=floCrypto.toFloID(user.id);if(!user_floID)return reject("Not an valid address");let last_key=`${floGlobals.application}|${user_floID}`;compactIDB.readData("lastTx",last_key,floDapps.root).then((lastTx=>{var query_options={pattern:floGlobals.application,tx:!0};"number"==typeof lastTx?query_options.ignoreOld=lastTx:"string"==typeof lastTx&&(query_options.after=lastTx),floBlockchainAPI.readData(user_floID,query_options).then((result=>{for(var i=result.items.length-1;i>=0;i--){let tx=result.items[i],content=JSON.parse(tx.data)[floGlobals.application];if(!(content instanceof Object))continue;let key=(content.type?content.type+"|":"")+tx.txid.substr(0,16);compactIDB.writeData("flodata",{time:tx.time,txid:tx.txid,data:content},key)}compactIDB.writeData("lastTx",result.lastItem,last_key,floDapps.root),resolve(!0)})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},MultiSig=rmMessenger.multisig={};MultiSig.createAddress=function(pubKeys,minRequired){return new Promise((async(resolve,reject)=>{let co_owners=pubKeys.map((p=>floCrypto.getFloID(p)));if(co_owners.includes(null))return reject("Invalid public key: "+pubKeys[co_owners.indexOf(null)]);let privateKey=await floDapps.user.private,multisig=btcOperator.multiSigAddress(pubKeys,minRequired);if("object"!=typeof multisig)return reject("Unable to create multisig address");let content={type:"btc_multisig",address:multisig.address,redeemScript:multisig.redeemScript};console.debug(content.address,content.redeemScript),floBlockchainAPI.writeDataMultiple([privateKey],JSON.stringify({[floGlobals.application]:content}),co_owners).then((txid=>{console.info(txid);let key="btc_multisig|"+txid.substr(0,16);compactIDB.writeData("flodata",{time:null,txid:txid,data:content},key),resolve(multisig.address)})).catch((error=>reject(error)))}))},MultiSig.listAddress=function(){return new Promise(((resolve,reject)=>{let options={lowerKey:"btc_multisig|",upperKey:"btc_multisig||"};compactIDB.searchData("flodata",options).then((result=>{let multsigs={};for(let i in result){let addr=result[i].data.address,addr_type=btcOperator.validateAddress(addr),decode=("multisig"==addr_type?coinjs.script().decodeRedeemScript:coinjs.script().decodeRedeemScriptBech32)(result[i].data.redeemScript);"multisig"!=addr_type&&"multisigBech32"!=addr_type?console.warn("Invalid multi-sig address:",addr):decode&&decode.address===addr?"multisig__"!==decode.type?console.warn("Redeem-script is not of a multisig:",addr):decode.pubkeys.includes(user.public.toLowerCase())||decode.pubkeys.includes(user.public.toUpperCase())?decode.pubkeys.lengthreject(error)))}))},MultiSig.createTx_BTC=function(address,redeemScript,receivers,amounts,fee=null,options={}){return new Promise((async(resolve,reject)=>{let addr_type=btcOperator.validateAddress(address);if("multisig"!=addr_type&&"multisigBech32"!=addr_type)return reject("Sender address is not a multisig");let decode=("multisig"==addr_type?coinjs.script().decodeRedeemScript:coinjs.script().decodeRedeemScriptBech32)(redeemScript);if(!decode||decode.address!==address||"multisig__"!==decode.type)return reject("Invalid redeem-script");if(!decode.pubkeys.includes(user.public.toLowerCase())&&!decode.pubkeys.includes(user.public.toUpperCase()))return reject("User is not a part of this multisig");if(decode.pubkeys.lengthfloCrypto.getFloID(p))),privateKey=await floDapps.user.private;btcOperator.createMultiSigTx(address,redeemScript,receivers,amounts,fee,options).then((({tx_hex:tx_hex})=>{tx_hex=btcOperator.signTx(tx_hex,privateKey),createPipeline("btc_multisig",co_owners,32,decode.pubkeys).then((pipeline=>{sendRaw(encrypt(tx_hex,pipeline.eKey),pipeline.id,"TRANSACTION",!1).then((result=>resolve(pipeline.id))).catch((error=>reject(error)))})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},MultiSig.signTx_BTC=function(pipeID){return new Promise(((resolve,reject)=>"btc_multisig"!==_loaded.pipeline[pipeID].model?reject("Incorrect pipeline model. Only works for BTC-multisig"):_loaded.pipeline[pipeID].disabled?reject("Pipeline is already closed"):void getChat(pipeID).then((async result=>{let pipeline=_loaded.pipeline[pipeID],tx_hex_latest=Object.keys(result).sort().map((i=>result[i].tx_hex)).filter((x=>x)).pop(),privateKey=await floDapps.user.private,tx_hex_signed=btcOperator.signTx(tx_hex_latest,privateKey);sendRaw(encrypt(tx_hex_signed,pipeline.eKey),pipeline.id,"TRANSACTION",!1).then((result=>{if(!btcOperator.checkSigned(tx_hex_signed))return resolve({tx_hex:tx_hex_signed});btcOperator.broadcastTx(tx_hex_signed).then((txid=>{console.debug(txid),sendRaw(encrypt(txid,pipeline.eKey),pipeline.id,"BROADCAST",!1).then((result=>resolve({tx_hex:tx_hex_signed,txid:txid}))).catch((error=>reject(error)))})).catch((error=>reject(error)))})).catch((error=>reject(error)))})).catch((error=>console.error(error)))))},MultiSig.createTx_FLO=function(address,redeemScript,receivers,amounts,floData="",options={}){return new Promise((async(resolve,reject)=>{if(!floCrypto.validateFloID(address)){let addr_type=btcOperator.validateAddress(address);if("multisig"!=addr_type&&"multisigBech32"!=addr_type)return reject("Sender address is not a multisig");address=floCrypto.toMultisigFloID(address)}let decode=floCrypto.decodeRedeemScript(redeemScript);if(!decode||decode.address!==address)return reject("Invalid redeem-script");if(!decode.pubkeys.includes(user.public.toLowerCase())&&!decode.pubkeys.includes(user.public.toUpperCase()))return reject("User is not a part of this multisig");if(decode.pubkeys.lengthfloCrypto.getFloID(p))),privateKey=await floDapps.user.private;floBlockchainAPI.createMultisigTx(redeemScript,receivers,amounts,floData).then((tx_hex=>{tx_hex=floBlockchainAPI.signTx(tx_hex,privateKey),createPipeline("flo_multisig",co_owners,32,decode.pubkeys).then((pipeline=>{sendRaw(encrypt(tx_hex,pipeline.eKey),pipeline.id,"TRANSACTION",!1).then((result=>resolve(pipeline.id))).catch((error=>reject(error)))})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},MultiSig.signTx_FLO=function(pipeID){return new Promise(((resolve,reject)=>"flo_multisig"!==_loaded.pipeline[pipeID].model?reject("Incorrect pipeline model. Only works for FLO-multisig"):_loaded.pipeline[pipeID].disabled?reject("Pipeline is already closed"):void getChat(pipeID).then((async result=>{let pipeline=_loaded.pipeline[pipeID],tx_hex_latest=Object.keys(result).sort().map((i=>result[i].tx_hex)).filter((x=>x)).pop(),privateKey=await floDapps.user.private,tx_hex_signed=floBlockchainAPI.signTx(tx_hex_latest,privateKey);sendRaw(encrypt(tx_hex_signed,pipeline.eKey),pipeline.id,"TRANSACTION",!1).then((result=>{if(!floBlockchainAPI.checkSigned(tx_hex_signed))return resolve({tx_hex:tx_hex_signed});floBlockchainAPI.broadcastTx(tx_hex_signed).then((txid=>{console.debug(txid),sendRaw(encrypt(txid,pipeline.eKey),pipeline.id,"BROADCAST",!1).then((result=>resolve({tx_hex:tx_hex_signed,txid:txid}))).catch((error=>reject(error)))})).catch((error=>reject(error)))})).catch((error=>reject(error)))})).catch((error=>console.error(error)))))};const createPipeline=rmMessenger.createPipeline=function(model,members,ekeySize=16,pubkeys=null){return new Promise(((resolve,reject)=>{if(null!==pubkeys){if(!Array.isArray(pubkeys))return reject("pubkeys must be an array (if passed)");if(pubkeys.length!==members.length)return reject("pubkey length doesnot match members length")}let imem1=[],imem2=[];if(members.forEach(((m,i)=>{floCrypto.validateAddr(m)?m in floGlobals.pubKeys||floCrypto.isSameAddr(user.id,m)||(null!==pubkeys&&floCrypto.verifyPubKey(pubkeys[i],m)?floGlobals.pubKeys[m]=pubkeys[i]:imem2.push(m)):imem1.push(m)})),imem1.length)return reject(`Invalid Members(floIDs): ${imem1}`);if(imem2.length)return reject(`Invalid Members (pubKey not available): ${imem2}`);let pipeline={id:floCrypto.tmpID,model:model,members:members};ekeySize&&(pipeline.eKey=floCrypto.randString(ekeySize));let pipelineInfo=JSON.stringify(pipeline),promises=members.filter((m=>!floCrypto.isSameAddr(m,user.id))).map((m=>sendRaw(pipelineInfo,m,"CREATE_PIPELINE",!0)));Promise.allSettled(promises).then((results=>{console.debug(results.filter((r=>"rejected"===r.status)).map((r=>r.reason))),_loaded.pipeline[pipeline.id]=Object.assign({},pipeline),pipeline.eKey&&(pipeline.eKey=encrypt(pipeline.eKey)),compactIDB.addData("pipeline",pipeline,pipeline.id).then((result=>{requestPipelineInbox(pipeline.id,pipeline.model),resolve(_loaded.pipeline[pipeline.id])})).catch((error=>reject(error)))}))}))};function requestPipelineInbox(pipeID,model,_async=!0){pipeConnID[pipeID]&&(floCloudAPI.closeRequest(pipeConnID[pipeID]),delete pipeConnID[pipeID]);let parseData=processData.pipeline[model](pipeID),fn=floCloudAPI.requestApplicationData(null,{receiverID:pipeID,lowerVectorClock:_loaded.appendix[`lastReceived_${pipeID}`]+1,callback:function(dataSet,error){if(error)return console.error(error);console.info(dataSet);let newInbox={messages:{}};for(let vc in dataSet)if(pipeID===dataSet[vc].receiverID)try{parseData(dataSet[vc],newInbox),floCrypto.isSameAddr(dataSet[vc].senderID,user.id)||addMark(pipeID,"unread"),(!_loaded.appendix[`lastReceived_${pipeID}`]||_loaded.appendix[`lastReceived_${pipeID}`]{fn.then((conn_id=>{pipeConnID[pipeID]=conn_id,resolve(`Connected to pipeline ${pipeID}`)})).catch((error=>reject(error)))}));fn.then((conn_id=>pipeConnID[pipeID]=conn_id)).catch((error=>console.error(`request-pipeline(${pipeID}):`,error)))}const disablePipeline=rmMessenger.disablePipeline=function(pipeID){return console.debug(JSON.stringify(pipeConnID),pipeConnID[pipeID]),new Promise(((resolve,reject)=>{if(!_loaded.pipeline[pipeID])return reject("Pipeline not found");if(_loaded.pipeline[pipeID].disabled)return resolve("Pipeline already diabled");_loaded.pipeline[pipeID].disabled=!0;let pipelineInfo=Object.assign({},_loaded.pipeline[pipeID]);pipelineInfo.eKey=encrypt(pipelineInfo.eKey),compactIDB.writeData("pipeline",pipelineInfo,pipeID).then((result=>{floCloudAPI.closeRequest(pipeConnID[pipeID]),delete pipeConnID[pipeID],resolve("Pipeline diabled")})).catch((error=>reject(error)))}))};rmMessenger.sendPipelineMessage=function(message,pipeID){return new Promise(((resolve,reject)=>{let k=_loaded.pipeline[pipeID].eKey;k&&(message=encrypt(message,k)),sendRaw(message,pipeID,"MESSAGE",!1).then((result=>resolve(`${pipeID}: ${message}`))).catch((error=>reject(error)))}))},rmMessenger.editFee=function(tx_id,new_fee,private_keys,change_only=!0){return new Promise((async(resolve,reject)=>{var address;Array.isArray(private_keys)||(private_keys=[private_keys]);try{let tx,tx_parsed;if(tx=await btcOperator.tx_fetch_for_editing(tx_id),tx_parsed=await btcOperator.parseTransaction(tx),tx_parsed.fee>=new_fee)return reject("Fees can only be increased");var edit_output_address=new Set;!0===change_only?tx_parsed.inputs.forEach((inp=>edit_output_address.add(inp.address))):!1===change_only?tx_parsed.outputs.forEach((out=>edit_output_address.add(out.address))):"string"==typeof change_only?edit_output_address.add(change_only):Array.isArray(change_only)&&change_only.forEach((id=>edit_output_address.add(id)));let inc_fee=btcOperator.util.BTC_to_Sat(new_fee-tx_parsed.fee);if(inc_fee=0&&inc_fee>0;i--)if(edit_output_address.has(tx_parsed.outputs[i].address)){let current_value=tx.outs[i].value;current_value instanceof BigInteger&&(current_value=current_value.intValue()),current_value>inc_fee?(tx.outs[i].value=current_value-inc_fee,inc_fee=0):(inc_fee-=current_value,tx.outs[i].value=0)}if(inc_fee>0){let max_possible_fee=btcOperator.util.BTC_to_Sat(new_fee)-inc_fee;return reject(`Insufficient output values to increase fee. Maximum fee possible: ${btcOperator.util.Sat_to_BTC(max_possible_fee)}`)}tx.outs=tx.outs.filter((o=>o.value>=DUST_AMT));let wif_keys=[],witness_position=0;for(let i in tx.ins){var addr=tx_parsed.inputs[i].address,value=btcOperator.util.BTC_to_Sat(tx_parsed.inputs[i].value);let addr_decode=coinjs.addressDecode(addr);var privKey=private_keys.find((pk=>verifyKey(addr,pk)));if(!privKey)return reject(`Private key missing for ${addr}`);const rs=_redeemScript(addr,privKey);var script;if(!1===rs?wif_keys.unshift(privKey):wif_keys.push(privKey),rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)){"bech32"==addr_decode&&(witness_position+=1);let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else if("multisigBech32"===addr_decode.type){address=addr;let redeemScript=btcOperator.extractLastHexStrings(tx.witness)[witness_position];witness_position+=1;let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(redeemScript)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else{let s=coinjs.script();s.writeOp(118),s.writeOp(169),s.writeBytes(addr_decode.bytes),s.writeOp(136),s.writeOp(172),script=Crypto.util.bytesToHex(s.buffer)}tx.ins[i].script=coinjs.script(script)}tx.witness=!1,console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1)));let tx_hex=tx.serialize(),addr_type=btcOperator.validateAddress(address);if("multisig"!=addr_type&&"multisigBech32"!=addr_type)return reject("Sender address is not a multisig");let decode=("multisig"==addr_type?coinjs.script().decodeRedeemScript:coinjs.script().decodeRedeemScriptBech32)(redeemScript);if(!decode||decode.address!==address||"multisig__"!==decode.type)return reject("Invalid redeem-script");if(!decode.pubkeys.includes(user.public.toLowerCase())&&!decode.pubkeys.includes(user.public.toUpperCase()))return reject("User is not a part of this multisig");if(decode.pubkeys.lengthfloCrypto.getFloID(p)));createPipeline("btc_multisig",co_owners,32,decode.pubkeys).then((pipeline=>{sendRaw(encrypt(tx_hex,pipeline.eKey),pipeline.id,"TRANSACTION",!1).then((result=>resolve(pipeline.id))).catch((error=>reject(error)))})).catch((error=>reject(error)))}catch(error){reject(error)}}))},processData.pipeline={},processData.pipeline.btc_multisig=function(pipeID){return(unparsed,newInbox)=>{if(!_loaded.pipeline[pipeID].members.includes(floCrypto.toFloID(unparsed.senderID)))return;let data={time:unparsed.time,sender:unparsed.senderID,pipeID:unparsed.receiverID},vc=unparsed.vectorClock,k=_loaded.pipeline[pipeID].eKey;switch(unparsed.message=decrypt(unparsed.message,k),floDapps.storePubKey(unparsed.senderID,unparsed.pubKey),data.type=unparsed.type,unparsed.type){case"TRANSACTION":data.tx_hex=unparsed.message;break;case"BROADCAST":data.txid=unparsed.message,btcOperator.getTx.hex(data.txid).then((tx_hex_final=>{getChat(pipeID).then((result=>{let tx_hex_inital=Object.keys(result).sort().map((i=>result[i].tx_hex)).filter((x=>x)).shift();btcOperator.checkIfSameTx(tx_hex_inital,tx_hex_final)&&disablePipeline(pipeID)})).catch((error=>console.error(error)))})).catch((error=>console.error(error)));break;case"MESSAGE":data.message=encrypt(unparsed.message)}compactIDB.addData("messages",Object.assign({},data),`${pipeID}|${vc}`),data.message&&(data.message=decrypt(data.message)),newInbox.messages[vc]=data}},processData.pipeline.flo_multisig=function(pipeID){return(unparsed,newInbox)=>{if(!_loaded.pipeline[pipeID].members.includes(floCrypto.toFloID(unparsed.senderID)))return;let data={time:unparsed.time,sender:unparsed.senderID,pipeID:unparsed.receiverID},vc=unparsed.vectorClock,k=_loaded.pipeline[pipeID].eKey;switch(unparsed.message=decrypt(unparsed.message,k),floDapps.storePubKey(unparsed.senderID,unparsed.pubKey),data.type=unparsed.type,unparsed.type){case"TRANSACTION":data.tx_hex=unparsed.message;break;case"BROADCAST":data.txid=unparsed.message,getChat(pipeID).then((result=>{var tx_hex_list=Object.keys(result).sort().map((i=>result[i].tx_hex)).filter((x=>x));let tx_hex_inital=tx_hex_list[0],tx_hex_final=tx_hex_list.pop();floBlockchainAPI.checkIfSameTx(tx_hex_inital,tx_hex_final)&&floBlockchainAPI.transactionID(tx_hex_final)==data.txid&&disablePipeline(pipeID)})).catch((error=>console.error(error)));break;case"MESSAGE":data.message=encrypt(unparsed.message)}compactIDB.addData("messages",Object.assign({},data),`${pipeID}|${vc}`),data.message&&(data.message=decrypt(data.message)),newInbox.messages[vc]=data}}}();