messenger/scripts/messenger.js
sairaj mote 38eacaa87f Bug fixes and UX improvements
-- implemented saving typed messages for every chat at changing chat and restore them when revisited
2022-06-29 15:18:39 +05:30

858 lines
36 KiB
JavaScript

(function () {
const messenger = window.messenger = {};
const expiredKeys = {};
const UI = {
group: (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)
};
messenger.renderUI = {};
Object.defineProperties(messenger.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
},
mails: {
set: ui_fn => UI.mails = ui_fn
},
marked: {
set: ui_fn => UI.marked = ui_fn
},
});
const _loaded = {};
Object.defineProperties(messenger, {
chats: {
get: () => _loaded.chats
},
groups: {
get: () => _loaded.groups
},
blocked: {
get: () => _loaded.blocked
},
marked: {
get: () => _loaded.marked
}
});
var directConnID, groupConnID = {};
messenger.conn = {};
Object.defineProperties(messenger.conn, {
direct: {
get: () => directConnID
},
group: {
get: () => Object.assign({}, groupConnID),
// value: g_id => groupConnID[g_id]
}
});
function sendRaw(message, recipient, type, encrypt = null, comment = undefined) {
return new Promise((resolve, reject) => {
if (!floCrypto.validateAddr(recipient))
return reject("Invalid Recipient floID");
if ([true, null].includes(encrypt) && recipient in floGlobals.pubKeys)
message = floCrypto.encryptData(message, floGlobals.pubKeys[recipient])
else if (encrypt === true)
return reject("recipient's pubKey not found")
let options = {
receiverID: recipient,
}
if (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)
result = [mark];
else if (!result.includes(mark))
result.push(mark);
else
return resolve("Mark already exist");
compactIDB.writeData("marked", result, key)
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
function removeMark(key, mark) {
return new Promise((resolve, reject) => {
compactIDB.readData("marked", key).then(result => {
if (!result || !result.includes(mark))
return resolve("Mark doesnot exist")
else {
result.splice(result.indexOf(mark), 1); //remove the mark from the list of marks
compactIDB.writeData("marked", result, key)
.then(result => resolve("Mark removed"))
.catch(error => reject(error))
}
}).catch(error => reject(error))
})
}
function requestGroupInbox(groupID) {
if (groupConnID[groupID]) { //close existing request connection (if any)
floCloudAPI.closeRequest(groupConnID[groupID]);
delete groupConnID[groupID];
}
let callbackFn = function (dataSet, error) {
if (error)
return console.error(error)
console.info(dataSet)
let newInbox = {
messages: {}
}
let infoChange = false;
for (let vc in dataSet) {
if (groupID !== dataSet[vc].receiverID ||
!_loaded.groups[groupID].members.includes(dataSet[vc].senderID))
continue;
try {
let data = {
time: dataSet[vc].time,
sender: dataSet[vc].senderID,
groupID: dataSet[vc].receiverID
}
let k = _loaded.groups[groupID].eKey;
if (expiredKeys[groupID]) {
var ex = Object.keys(expiredKeys[groupID]).sort()
while (ex.length && vc > ex[0]) ex.shift()
if (ex.length)
k = expiredKeys[groupID][ex.shift()]
}
dataSet[vc].message = decrypt(dataSet[vc].message, k)
//store the pubKey if not stored already
floDapps.storePubKey(dataSet[vc].senderID, dataSet[vc].pubKey)
if (dataSet[vc].type === "GROUP_MSG")
data.message = encrypt(dataSet[vc].message);
else if (data.sender === _loaded.groups[groupID].admin) {
let groupInfo = _loaded.groups[groupID]
data.admin = true;
if (dataSet[vc].type === "ADD_MEMBERS") {
data.newMembers = dataSet[vc].message.split("|")
data.note = dataSet[vc].comment
groupInfo.members = [...new Set(groupInfo.members.concat(data.newMembers))]
} else if (dataSet[vc].type === "RM_MEMBERS") {
data.rmMembers = dataSet[vc].message.split("|")
data.note = dataSet[vc].comment
groupInfo.members = groupInfo.members.filter(m => !data.rmMembers.includes(m))
if (data.rmMembers.includes(myFloID)) {
disableGroup(groupID);
return;
}
} else if (dataSet[vc].type === "UP_DESCRIPTION") {
data.description = dataSet[vc].message
groupInfo.description = data.description
} else if (dataSet[vc].type === "UP_NAME") {
data.name = dataSet[vc].message
groupInfo.name = data.name
}
infoChange = true;
}
compactIDB.addData("messages", {
...data
}, `${groupID}|${vc}`)
if (data.message)
data.message = decrypt(data.message);
newInbox.messages[vc] = data;
if (data.sender !== myFloID)
addMark(data.groupID, "unread")
if (!_loaded.appendix[`lastReceived_${groupID}`] ||
_loaded.appendix[`lastReceived_${groupID}`] < vc)
_loaded.appendix[`lastReceived_${groupID}`] = vc;
} catch (error) {
console.log(error)
}
}
compactIDB.writeData("appendix", _loaded.appendix[`lastReceived_${groupID}`],
`lastReceived_${groupID}`);
if (infoChange) {
let newInfo = {
..._loaded.groups[groupID]
}
newInfo.eKey = encrypt(newInfo.eKey)
compactIDB.writeData("groups", newInfo, groupID)
}
console.debug(newInbox);
UI.group(newInbox);
}
floCloudAPI.requestApplicationData(null, {
receiverID: groupID,
lowerVectorClock: _loaded.appendix[`lastReceived_${groupID}`] + 1,
callback: callbackFn
}).then(conn_id => groupConnID[groupID] = conn_id)
.catch(error => console.error(`request-group(${groupID}):`, error))
}
const initUserDB = function () {
return new Promise((resolve, reject) => {
var obj = {
messages: {},
mails: {},
marked: {},
chats: {},
groups: {},
gkeys: {},
blocked: {},
appendix: {},
userSettings: {}
}
compactIDB.initDB(`${floGlobals.application}_${myFloID}`, obj).then(result => {
console.info(result)
compactIDB.setDefaultDB(`${floGlobals.application}_${myFloID}`);
resolve("Messenger UserDB Initated Successfully")
}).catch(error => reject(error));
})
}
messenger.blockUser = function (floID) {
return new Promise((resolve, reject) => {
if (_loaded.blocked.has(floID))
return resolve("User is already blocked");
compactIDB.addData("blocked", true, floID).then(result => {
_loaded.blocked.add(floID);
resolve("Blocked User: " + floID);
}).catch(error => reject(error))
})
}
messenger.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))
})
}
messenger.sendMessage = function (message, receiver) {
return new Promise((resolve, reject) => {
sendRaw(message, receiver, "MESSAGE").then(result => {
let vc = result.vectorClock;
let 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", {
...data
}, `${receiver}|${vc}`)
data.message = message;
resolve({
[vc]: data
});
}).catch(error => reject(error))
})
}
messenger.sendMail = function (subject, content, recipients, prev = null) {
return new Promise((resolve, reject) => {
if (!Array.isArray(recipients))
recipients = [recipients]
let mail = {
subject: subject,
content: content,
ref: Date.now() + floCrypto.randString(8, true),
prev: prev
}
let promises = recipients.map(r => sendRaw(JSON.stringify(mail), r, "MAIL"))
Promise.allSettled(promises).then(results => {
mail.time = Date.now();
mail.from = myFloID
mail.to = []
results.forEach(r => {
if (r.status === "fulfilled")
mail.to.push(r.value.receiverID)
});
if (mail.to.length === 0)
return reject(results)
mail.content = encrypt(content)
compactIDB.addData("mails", {
...mail
}, mail.ref)
mail.content = content
resolve({
[mail.ref]: mail
});
})
})
}
const requestDirectInbox = function () {
if (directConnID) { //close existing request connection (if any)
floCloudAPI.closeRequest(directConnID);
directConnID = undefined;
}
let callbackFn = function (dataSet, error) {
if (error)
return console.error(error)
let newInbox = {
messages: {},
mails: {},
newgroups: [],
keyrevoke: []
}
for (let vc in dataSet) {
try {
//store the pubKey if not stored already
floDapps.storePubKey(dataSet[vc].senderID, dataSet[vc].pubKey);
if (_loaded.blocked.has(dataSet[vc].senderID) && dataSet[vc].type !== "REVOKE_KEY")
throw "blocked-user";
if (dataSet[vc].message instanceof Object && "secret" in dataSet[vc].message)
dataSet[vc].message = floCrypto.decryptData(dataSet[vc].message, myPrivKey)
if (dataSet[vc].type === "MESSAGE") {
//process as message
let dm = {
time: dataSet[vc].time,
floID: dataSet[vc].senderID,
category: "received",
message: encrypt(dataSet[vc].message)
}
compactIDB.addData("messages", {
...dm
}, `${dm.floID}|${vc}`)
_loaded.chats[dm.floID] = parseInt(vc)
compactIDB.writeData("chats", parseInt(vc), dm.floID)
dm.message = dataSet[vc].message;
newInbox.messages[vc] = dm;
addMark(dm.floID, "unread")
} else if (dataSet[vc].type === "MAIL") {
//process as mail
let data = JSON.parse(dataSet[vc].message);
let mail = {
time: dataSet[vc].time,
from: dataSet[vc].senderID,
to: [myFloID],
subject: data.subject,
content: encrypt(data.content),
ref: data.ref,
prev: data.prev
}
compactIDB.addData("mails", {
...mail
}, mail.ref);
mail.content = data.content;
newInbox.mails[mail.ref] = mail;
addMark(mail.ref, "unread")
} else if (dataSet[vc].type === "CREATE_GROUP") {
//process create group
let groupInfo = JSON.parse(dataSet[vc].message);
let h = ["groupID", "created", "admin"].map(x => groupInfo[x]).join('|')
if (groupInfo.admin === dataSet[vc].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", {
...groupInfo
}, groupInfo.groupID)
groupInfo.eKey = eKey
_loaded.groups[groupInfo.groupID] = groupInfo
requestGroupInbox(groupInfo.groupID)
newInbox.newgroups.push(groupInfo.groupID)
}
} else if (dataSet[vc].type === "REVOKE_KEY") {
let r = JSON.parse(dataSet[vc].message);
let groupInfo = _loaded.groups[r.groupID]
if (dataSet[vc].senderID === groupInfo.admin) {
if (typeof expiredKeys[r.groupID] !== "object")
expiredKeys[r.groupID] = {}
expiredKeys[r.groupID][vc] = groupInfo.eKey
let eKey = r.newKey
groupInfo.eKey = encrypt(eKey);
compactIDB.writeData("groups", {
...groupInfo
}, groupInfo.groupID)
groupInfo.eKey = eKey
newInbox.keyrevoke.push(groupInfo.groupID)
}
}
} catch (error) {
//if (error !== "blocked-user")
console.log(error);
} finally {
if (_loaded.appendix.lastReceived < vc)
_loaded.appendix.lastReceived = vc;
}
}
compactIDB.writeData("appendix", _loaded.appendix.lastReceived, "lastReceived");
console.debug(newInbox);
UI.direct(newInbox)
}
floCloudAPI.requestApplicationData(null, {
receiverID: myFloID,
lowerVectorClock: _loaded.appendix.lastReceived + 1,
callback: callbackFn
}).then(conn_id => directConnID = conn_id)
.catch(error => console.error("request-direct:", error));
}
messenger.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 = messenger.getChatOrder = function (separate = false) {
let result;
if (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]);
} else {
result = Object.keys(_loaded.chats).map(a => [_loaded.chats[a], a])
.concat(Object.keys(_loaded.groups).map(a => [parseInt(_loaded.appendix[`lastReceived_${a}`]), a]))
.sort((a, b) => b[0] - a[0]).map(a => a[1])
}
return result;
}
messenger.storeContact = function (floID, name) {
return floDapps.storeContact(floID, name)
}
const loadDataFromIDB = function (defaultList = true) {
return new Promise((resolve, reject) => {
if (defaultList)
dataList = ["mails", "marked", "groups", "chats", "blocked", "appendix"]
else
dataList = ["messages", "mails", "marked", "chats", "groups", "gkeys", "blocked", "appendix"]
let promises = []
for (var i = 0; i < dataList.length; i++)
promises[i] = compactIDB.readAllData(dataList[i])
Promise.all(promises).then(results => {
let data = {}
for (var i = 0; i < dataList.length; i++)
data[dataList[i]] = results[i]
data.appendix.lastReceived = data.appendix.lastReceived || '0';
if (data.appendix.AESKey) {
try {
let AESKey = floCrypto.decryptData(data.appendix.AESKey, myPrivKey);
data.appendix.AESKey = AESKey;
if (dataList.includes("messages"))
for (let m in data.messages)
if (data.messages[m].message)
data.messages[m].message = decrypt(data.messages[m].message, AESKey);
if (dataList.includes("mails"))
for (let m in data.mails)
data.mails[m].content = decrypt(data.mails[m].content, AESKey);
if (dataList.includes("groups"))
for (let g in data.groups)
data.groups[g].eKey = decrypt(data.groups[g].eKey, AESKey);
if (dataList.includes("gkeys"))
for (let k in data.gkeys)
data.gkeys[k] = decrypt(data.gkeys[k], AESKey);
resolve(data)
} catch (error) {
reject("Corrupted AES Key");
}
} else {
if (Object.keys(data.mails).length)
return reject("AES Key not Found")
let AESKey = floCrypto.randString(32, false);
let encryptedKey = floCrypto.encryptData(AESKey, myPubKey);
compactIDB.addData("appendix", encryptedKey, "AESKey").then(result => {
data.appendix.AESKey = AESKey;
resolve(data);
}).catch(error => reject("Unable to Generate AES Key"))
}
}).catch(error => reject(error))
})
}
messenger.addMark = function (key, mark) {
if (_loaded.marked.hasOwnProperty(key) && !_loaded.marked[key].includes(mark))
_loaded.marked[key].push(mark)
return addMark(key, mark)
}
messenger.removeMark = function (key, mark) {
if (_loaded.marked.hasOwnProperty(key))
_loaded.marked[key] = _loaded.marked[key].filter(v => v !== mark)
return removeMark(key, mark)
}
messenger.addChat = function (chatID) {
return new Promise((resolve, reject) => {
compactIDB.addData("chats", 0, chatID)
.then(result => resolve("Added chat"))
.catch(error => reject(error))
})
}
messenger.rmChat = function (chatID) {
return new Promise((resolve, reject) => {
compactIDB.removeData("chats", chatID)
.then(result => resolve("Chat removed"))
.catch(error => reject(error))
})
}
messenger.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))
})
}
messenger.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)
if (result[i].message)
result[i].message = decrypt(result[i].message)
resolve(result)
}).catch(error => reject(error))
})
}
messenger.backupData = function () {
return new Promise((resolve, reject) => {
loadDataFromIDB(false).then(data => {
delete data.appendix.AESKey;
data.contacts = floGlobals.contacts;
data.pubKeys = floGlobals.pubKeys;
data = btoa(unescape(encodeURIComponent(JSON.stringify(data))))
let blobData = {
floID: myFloID,
pubKey: myPubKey,
data: encrypt(data, myPrivKey),
}
blobData.sign = floCrypto.signData(blobData.data, myPrivKey);
resolve(new Blob([JSON.stringify(blobData)], {
type: 'application/json'
}));
}).catch(error => reject(error))
})
}
const parseBackup = messenger.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))
reject("Corrupted Backup file: Signature verification failed");
else if (myFloID !== blobData.floID || myPubKey !== blobData.pubKey)
reject("Invalid Backup file: Incorrect floID");
else {
try {
let data = decrypt(blobData.data, myPrivKey)
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");
}
}
}
reader.readAsText(blob);
} else
reject("Backup is not a valid File (or) Blob")
})
}
messenger.restoreData = function (arg) {
return new Promise((resolve, reject) => {
if (arg instanceof Blob || arg instanceof File)
var parseData = parseBackup
else
var parseData = data => new Promise((res, rej) => res(data))
parseData(arg).then(data => {
for (let m in data.messages)
if (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 c in data.chats)
if (data.chats[c] <= _loaded.chats[c])
delete data.chats[c]
for (let l in data.appendix)
if (l.startsWith('lastReceived') && data.appendix[l] <= _loaded.appendix[l])
delete data.appendix[l]
for (let c in data.contacts)
if (c in floGlobals.contacts)
delete data.contacts[c]
for (let p in data.pubKeys)
if (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);
break;
}
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))
})
}
messenger.clearUserData = function () {
return new Promise((resolve, reject) => {
let promises = [
compactIDB.deleteDB(),
floDapps.clearCredentials()
]
Promise.all(promises)
.then(result => resolve("User Data cleared"))
.catch(error => reject(error))
})
}
//group feature
messenger.createGroup = function (groupname, description = '') {
return new Promise((resolve, reject) => {
if (!groupname) return reject("Invalid Group Name")
let id = floCrypto.generateNewID();
let groupInfo = {
groupID: id.floID,
pubKey: id.pubKey,
admin: myFloID,
name: groupname,
description: description,
created: Date.now(),
members: [myFloID]
}
let h = ["groupID", "created", "admin"].map(x => groupInfo[x]).join('|')
groupInfo.hash = floCrypto.signData(h, id.privKey)
let eKey = floCrypto.randString(16, false)
groupInfo.eKey = encrypt(eKey)
let p1 = compactIDB.addData("groups", groupInfo, id.floID)
let 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))
})
}
messenger.changeGroupName = function (groupID, name) {
return new Promise((resolve, reject) => {
let groupInfo = _loaded.groups[groupID]
if (myFloID !== groupInfo.admin)
return reject("Access denied: Admin only!")
let message = encrypt(name, groupInfo.eKey)
sendRaw(message, groupID, "UP_NAME", false)
.then(result => resolve('Name updated'))
.catch(error => reject(error))
})
}
messenger.changeGroupDescription = function (groupID, description) {
return new Promise((resolve, reject) => {
let groupInfo = _loaded.groups[groupID]
if (myFloID !== groupInfo.admin)
return reject("Access denied: Admin only!")
let message = encrypt(description, groupInfo.eKey)
sendRaw(message, groupID, "UP_DESCRIPTION", false)
.then(result => resolve('Description updated'))
.catch(error => reject(error))
})
}
messenger.addGroupMembers = function (groupID, newMem, note = undefined) {
return new Promise((resolve, reject) => {
if (!Array.isArray(newMem) && typeof newMem === "string")
newMem = [newMem]
//check for validity
let imem1 = [],
imem2 = []
newMem.forEach(m =>
!floCrypto.validateAddr(m) ? imem1.push(m) :
m in floGlobals.pubKeys ? null : imem2.push(m)
);
if (imem1.length)
return reject(`Invalid Members(floIDs): ${imem1}`)
else if (imem2.length)
return reject(`Invalid Members (pubKey not available): ${imem2}`)
//send new newMem list to existing members
let groupInfo = _loaded.groups[groupID]
if (myFloID !== groupInfo.admin)
return reject("Access denied: Admin only!")
let k = groupInfo.eKey;
//send groupInfo to new newMem
groupInfo = JSON.stringify(groupInfo)
let promises = newMem.map(m => sendRaw(groupInfo, m, "CREATE_GROUP", true));
Promise.allSettled(promises).then(results => {
let success = [],
failed = [];
for (let i in results)
if (results[i].status === "fulfilled")
success.push(newMem[i])
else if (results[i].status === "rejected")
failed.push(newMem[i])
let message = encrypt(success.join("|"), k)
sendRaw(message, groupID, "ADD_MEMBERS", false, note)
.then(r => resolve(`Members added: ${success}`))
.catch(e => reject(e))
})
})
}
messenger.rmGroupMembers = function (groupID, rmMem, note = undefined) {
return new Promise((resolve, reject) => {
if (!Array.isArray(rmMem) && typeof rmMem === "string")
rmMem = [rmMem]
let groupInfo = _loaded.groups[groupID]
let imem = rmMem.filter(m => !groupInfo.members.includes(m))
if (imem.length)
return reject(`Invalid members: ${imem}`)
if (myFloID !== groupInfo.admin)
return reject("Access denied: Admin only!")
let message = encrypt(rmMem.join("|"), groupInfo.eKey)
let p1 = sendRaw(message, groupID, "RM_MEMBERS", false, 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 = messenger.revokeKey = function (groupID) {
return new Promise((resolve, reject) => {
let groupInfo = _loaded.groups[groupID]
if (myFloID !== groupInfo.admin)
return reject("Access denied: Admin only!")
let newKey = floCrypto.randString(16, false);
Promise.all(groupInfo.members.map(m => sendRaw(JSON.stringify({
newKey,
groupID
}), m, "REVOKE_KEY", true))).then(result => {
resolve("Group key revoked")
}).catch(error => reject(error))
})
}
messenger.sendGroupMessage = function (message, groupID) {
return new Promise((resolve, reject) => {
let k = _loaded.groups[groupID].eKey
message = encrypt(message, k)
sendRaw(message, groupID, "GROUP_MSG", false)
.then(result => resolve(`${groupID}: ${message}`))
.catch(error => reject(error))
})
}
const disableGroup = messenger.disableGroup = function (groupID) {
return new Promise((resolve, reject) => {
if (!_loaded.groups[groupID])
return reject("Group not found");
let groupInfo = {
..._loaded.groups[groupID]
};
if (groupInfo.disabled)
return resolve("Group already diabled");
groupInfo.disabled = true;
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))
})
}
messenger.init = function () {
return new Promise((resolve, reject) => {
initUserDB().then(result => {
console.debug(result);
loadDataFromIDB().then(data => {
console.debug(data);
//load data to memory
_loaded.appendix = data.appendix;
_loaded.groups = data.groups;
_loaded.chats = data.chats;
_loaded.marked = data.marked;
_loaded.blocked = new Set(Object.keys(data.blocked));
//call UI render functions
UI.chats(getChatOrder());
UI.mails(data.mails);
UI.marked(data.marked);
//request data from cloud
requestDirectInbox();
for (let g in data.groups)
if (data.groups[g].disabled !== true)
requestGroupInbox(g);
resolve("Messenger initiated");
}).catch(error => reject(error));
})
})
}
})();