diff --git a/scripts/floCloudAPI.js b/scripts/floCloudAPI.js index c2d2c3f..db50d11 100644 --- a/scripts/floCloudAPI.js +++ b/scripts/floCloudAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCloudAPI v2.4.3a +(function (EXPORTS) { //floCloudAPI v2.4.5 /* FLO Cloud operations to send/request application data*/ 'use strict'; const floCloudAPI = EXPORTS; @@ -609,49 +609,39 @@ }) } */ - /*(NEEDS UPDATE) - //edit comment of data in supernode cloud (mutable comments only) - floCloudAPI.editApplicationData = function(vectorClock, newComment, oldData, options = {}) { + //edit comment of data in supernode cloud (sender only) + floCloudAPI.editApplicationData = function (vectorClock, comment_edit, options = {}) { return new Promise((resolve, reject) => { - let p0 - if (!oldData) { - options.atVectorClock = vectorClock; - options.callback = false; - p0 = requestApplicationData(false, options) - } else - p0 = Promise.resolve({ - vectorClock: { - ...oldData - } - }) - p0.then(d => { - if (d.senderID != user.id) - return reject("Invalid requestorID") - else if (!d.comment.startsWith("EDIT:")) - return reject("Data immutable") - let data = { + //request the data from cloud for resigning + let req_options = Object.assign({}, options); + req_options.atVectorClock = vectorClock; + requestApplicationData(undefined, req_options).then(result => { + if (!result.length) + return reject("Data not found"); + let data = result[0]; + if (data.senderID !== user.id) + return reject("Only sender can edit comment"); + data.comment = comment_edit; + let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"] + .map(d => data[d]).join("|"); + let re_sign = user.sign(hashcontent); + var request = { + receiverID: options.receiverID || DEFAULT.adminID, requestorID: user.id, - receiverID: d.receiverID, + pubKey: user.public, time: Date.now(), - application: d.application, - edit: { - vectorClock: vectorClock, - comment: newComment - } + vectorClock: vectorClock, + edit: comment_edit, + re_sign: re_sign } - d.comment = data.edit.comment; - let hashcontent = ["receiverID", "time", "application", "type", "message", - "comment" - ] - .map(x => d[x]).join("|") - data.edit.sign = user.sign(hashcontent) - singleRequest(data.receiverID, data) - .then(result => resolve("Data comment updated")) + let request_hash = ["time", "vectorClock", "edit", "re_sign"].map(d => request[d]).join("|"); + request.sign = user.sign(request_hash); + singleRequest(request.receiverID, request) + .then(result => resolve(result)) .catch(error => reject(error)) - }) + }).catch(error => reject(error)) }) } - */ //tag data in supernode cloud (subAdmin access only) floCloudAPI.tagApplicationData = function (vectorClock, tag, options = {}) { @@ -811,6 +801,67 @@ }) } + //upload file + floCloudAPI.uploadFile = function (fileBlob, type, options = {}) { + return new Promise((resolve, reject) => { + if (!(fileBlob instanceof File) && !(fileBlob instanceof Blob)) + return reject("file must be instance of File/Blob"); + fileBlob.arrayBuffer().then(arraybuf => { + let file_data = { type: fileBlob.type, name: fileBlob.name }; + file_data.content = Crypto.util.bytesToBase64(new Uint8Array(arraybuf)); + if (options.encrypt) { + let encryptionKey = options.encrypt === true ? + floGlobals.settings.encryptionKey : options.encrypt + file_data = floCrypto.encryptData(JSON.stringify(file_data), encryptionKey) + } + sendApplicationData(file_data, type, options) + .then(({ vectorClock, receiverID, type, application }) => resolve({ vectorClock, receiverID, type, application })) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }) + } + + //download file + floCloudAPI.downloadFile = function (vectorClock, options = {}) { + return new Promise((resolve, reject) => { + options.atVectorClock = vectorClock; + requestApplicationData(options.type, options).then(result => { + if (!result.length) + return reject("File not found"); + result = result[0]; + try { + let file_data = decodeMessage(result.message); + //file is encrypted: decryption required + if (file_data instanceof Object && "secret" in file_data) { + if (!options.decrypt) + return reject("Data is encrypted"); + let decryptionKey = (options.decrypt === true) ? Crypto.AES.decrypt(user_private, aes_key) : options.decrypt; + if (!Array.isArray(decryptionKey)) + decryptionKey = [decryptionKey]; + let flag = false; + for (let key of decryptionKey) { + try { + let tmp = floCrypto.decryptData(file_data, key); + file_data = JSON.parse(tmp); + flag = true; + break; + } catch (error) { } + } + if (!flag) + return reject("Unable to decrypt file: Invalid private key"); + } + //reconstruct the file + let arraybuf = new Uint8Array(Crypto.util.base64ToBytes(file_data.content)) + result.file = new File([arraybuf], file_data.name, { type: file_data.type }); + resolve(result) + } catch (error) { + console.error(error); + reject("Data is not a file"); + } + }).catch(error => reject(error)) + }) + } + /* Functions: findDiff(original, updatedObj) returns an object with the added, deleted and updated differences diff --git a/scripts/floDapps.js b/scripts/floDapps.js index 4d525a2..480e4e4 100644 --- a/scripts/floDapps.js +++ b/scripts/floDapps.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floDapps v2.4.0 +(function (EXPORTS) { //floDapps v2.4.1 /* General functions for FLO Dapps*/ 'use strict'; const floDapps = EXPORTS; @@ -172,12 +172,7 @@ //general lastTx: {}, //supernode (cloud list) - supernodes: { - indexes: { - uri: null, - pubKey: null - } - } + supernodes: {} } var obs_a = { //login credentials @@ -257,7 +252,8 @@ return new Promise((resolve, reject) => { if (!startUpOptions.cloud) return resolve("No cloud for this app"); - compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => { + const CLOUD_KEY = "floCloudAPI#" + floCloudAPI.SNStorageID; + compactIDB.readData("lastTx", CLOUD_KEY, DEFAULT.root).then(lastTx => { var query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName }; if (typeof lastTx == 'number') //lastTx is tx count (*backward support) query_options.ignoreOld = lastTx; @@ -265,25 +261,27 @@ query_options.after = lastTx; //fetch data from flosight floBlockchainAPI.readData(floCloudAPI.SNStorageID, query_options).then(result => { - 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) - compactIDB.removeData("supernodes", sn, DEFAULT.root); - for (let sn in content.newNodes) - compactIDB.writeData("supernodes", content.newNodes[sn], sn, DEFAULT.root); - for (let sn in content.updateNodes) - compactIDB.readData("supernodes", sn, DEFAULT.root).then(r => { - r = r || {} - r.uri = content.updateNodes[sn]; - compactIDB.writeData("supernodes", r, sn, DEFAULT.root); - }); - } - compactIDB.writeData("lastTx", result.lastItem, floCloudAPI.SNStorageID, DEFAULT.root); - compactIDB.readAllData("supernodes", DEFAULT.root).then(nodes => { - floCloudAPI.init(nodes) - .then(result => resolve("Loaded Supernode list\n" + result)) - .catch(error => reject(error)) - }) + 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) + if (sn in nodes) //check if node is listed + 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)) })