commit
37edbfc461
259
compactIDB.js
Normal file
259
compactIDB.js
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
(function(EXPORTS) { //compactIDB v2.1.0
|
||||||
|
/* Compact IndexedDB operations */
|
||||||
|
'use strict';
|
||||||
|
const compactIDB = EXPORTS;
|
||||||
|
|
||||||
|
var defaultDB;
|
||||||
|
|
||||||
|
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||||
|
const IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
|
||||||
|
const IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
|
||||||
|
|
||||||
|
if (!indexedDB) {
|
||||||
|
console.error("Your browser doesn't support a stable version of IndexedDB.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
compactIDB.setDefaultDB = dbName => defaultDB = dbName;
|
||||||
|
|
||||||
|
Object.defineProperty(compactIDB, 'default', {
|
||||||
|
get: () => defaultDB,
|
||||||
|
set: dbName => defaultDB = dbName
|
||||||
|
});
|
||||||
|
|
||||||
|
function getDBversion(dbName = defaultDB) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openDB(dbName).then(db => {
|
||||||
|
resolve(db.version)
|
||||||
|
db.close()
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function upgradeDB(dbName, createList = null, deleteList = null) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getDBversion(dbName).then(version => {
|
||||||
|
var idb = indexedDB.open(dbName, version + 1);
|
||||||
|
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||||
|
idb.onupgradeneeded = (event) => {
|
||||||
|
let db = event.target.result;
|
||||||
|
if (createList instanceof Object) {
|
||||||
|
if (Array.isArray(createList)) {
|
||||||
|
let tmp = {}
|
||||||
|
createList.forEach(o => tmp[o] = {})
|
||||||
|
createList = tmp
|
||||||
|
}
|
||||||
|
for (let o in createList) {
|
||||||
|
let obs = db.createObjectStore(o, createList[o].options || {});
|
||||||
|
if (createList[o].indexes instanceof Object)
|
||||||
|
for (let i in createList[o].indexes)
|
||||||
|
obs.createIndex(i, i, createList[o].indexes || {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(deleteList))
|
||||||
|
deleteList.forEach(o => db.deleteObjectStore(o));
|
||||||
|
resolve('Database upgraded')
|
||||||
|
}
|
||||||
|
idb.onsuccess = (event) => event.target.result.close();
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
compactIDB.initDB = function(dbName, objectStores = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!(objectStores instanceof Object))
|
||||||
|
return reject('ObjectStores must be an object or array')
|
||||||
|
defaultDB = defaultDB || dbName;
|
||||||
|
var idb = indexedDB.open(dbName);
|
||||||
|
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||||
|
idb.onsuccess = (event) => {
|
||||||
|
var db = event.target.result;
|
||||||
|
let cList = Object.values(db.objectStoreNames);
|
||||||
|
var obs = {},
|
||||||
|
a_obs = {},
|
||||||
|
d_obs = [];
|
||||||
|
if (!Array.isArray(objectStores))
|
||||||
|
var obs = objectStores
|
||||||
|
else
|
||||||
|
objectStores.forEach(o => obs[o] = {})
|
||||||
|
let nList = Object.keys(obs)
|
||||||
|
for (let o of nList)
|
||||||
|
if (!cList.includes(o))
|
||||||
|
a_obs[o] = obs[o]
|
||||||
|
for (let o of cList)
|
||||||
|
if (!nList.includes(o))
|
||||||
|
d_obs.push(o)
|
||||||
|
if (!Object.keys(a_obs).length && !d_obs.length)
|
||||||
|
resolve("Initiated IndexedDB");
|
||||||
|
else
|
||||||
|
upgradeDB(dbName, a_obs, d_obs)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const openDB = compactIDB.openDB = function(dbName = defaultDB) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var idb = indexedDB.open(dbName);
|
||||||
|
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||||
|
idb.onupgradeneeded = (event) => {
|
||||||
|
event.target.result.close();
|
||||||
|
deleteDB(dbName).then(_ => null).catch(_ => null).finally(_ => reject("Datebase not found"))
|
||||||
|
}
|
||||||
|
idb.onsuccess = (event) => resolve(event.target.result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteDB = compactIDB.deleteDB = function(dbName = defaultDB) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var deleteReq = indexedDB.deleteDatabase(dbName);;
|
||||||
|
deleteReq.onerror = (event) => reject("Error deleting database!");
|
||||||
|
deleteReq.onsuccess = (event) => resolve("Database deleted successfully");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compactIDB.writeData = function(obsName, data, key = false, dbName = defaultDB) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openDB(dbName).then(db => {
|
||||||
|
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||||
|
let writeReq = (key ? obs.put(data, key) : obs.put(data));
|
||||||
|
writeReq.onsuccess = (evt) => resolve(`Write data Successful`);
|
||||||
|
writeReq.onerror = (evt) => reject(
|
||||||
|
`Write data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||||
|
);
|
||||||
|
db.close();
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compactIDB.addData = function(obsName, data, key = false, dbName = defaultDB) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openDB(dbName).then(db => {
|
||||||
|
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||||
|
let addReq = (key ? obs.add(data, key) : obs.add(data));
|
||||||
|
addReq.onsuccess = (evt) => resolve(`Add data successful`);
|
||||||
|
addReq.onerror = (evt) => reject(
|
||||||
|
`Add data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||||
|
);
|
||||||
|
db.close();
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compactIDB.removeData = function(obsName, key, dbName = defaultDB) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openDB(dbName).then(db => {
|
||||||
|
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||||
|
let delReq = obs.delete(key);
|
||||||
|
delReq.onsuccess = (evt) => resolve(`Removed Data ${key}`);
|
||||||
|
delReq.onerror = (evt) => reject(
|
||||||
|
`Remove data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||||
|
);
|
||||||
|
db.close();
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compactIDB.clearData = function(obsName, dbName = defaultDB) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openDB(dbName).then(db => {
|
||||||
|
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||||
|
let clearReq = obs.clear();
|
||||||
|
clearReq.onsuccess = (evt) => resolve(`Clear data Successful`);
|
||||||
|
clearReq.onerror = (evt) => reject(`Clear data Unsuccessful`);
|
||||||
|
db.close();
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compactIDB.readData = function(obsName, key, dbName = defaultDB) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openDB(dbName).then(db => {
|
||||||
|
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||||
|
let getReq = obs.get(key);
|
||||||
|
getReq.onsuccess = (evt) => resolve(evt.target.result);
|
||||||
|
getReq.onerror = (evt) => reject(
|
||||||
|
`Read data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||||
|
);
|
||||||
|
db.close();
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compactIDB.readAllData = function(obsName, dbName = defaultDB) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openDB(dbName).then(db => {
|
||||||
|
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||||
|
var tmpResult = {}
|
||||||
|
let curReq = obs.openCursor();
|
||||||
|
curReq.onsuccess = (evt) => {
|
||||||
|
var cursor = evt.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
tmpResult[cursor.primaryKey] = cursor.value;
|
||||||
|
cursor.continue();
|
||||||
|
} else
|
||||||
|
resolve(tmpResult);
|
||||||
|
}
|
||||||
|
curReq.onerror = (evt) => reject(
|
||||||
|
`Read-All data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||||
|
);
|
||||||
|
db.close();
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openDB(dbName).then(db => {
|
||||||
|
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||||
|
var filteredResult = {}
|
||||||
|
let keyRange;
|
||||||
|
if(options.lowerKey!==null && options.upperKey!==null)
|
||||||
|
keyRange = IDBKeyRange.bound(options.lowerKey, options.upperKey);
|
||||||
|
else if(options.lowerKey!==null)
|
||||||
|
keyRange = IDBKeyRange.lowerBound(options.lowerKey);
|
||||||
|
else if (options.upperKey!==null)
|
||||||
|
keyRange = IDBKeyRange.upperBound(options.upperBound);
|
||||||
|
else if (options.atKey)
|
||||||
|
let curReq = obs.openCursor(keyRange, )
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}*/
|
||||||
|
|
||||||
|
compactIDB.searchData = function(obsName, options = {}, dbName = defaultDB) {
|
||||||
|
options.lowerKey = options.atKey || options.lowerKey || 0
|
||||||
|
options.upperKey = options.atKey || options.upperKey || false
|
||||||
|
options.patternEval = options.patternEval || ((k, v) => {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
options.limit = options.limit || false;
|
||||||
|
options.lastOnly = options.lastOnly || false
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
openDB(dbName).then(db => {
|
||||||
|
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||||
|
var filteredResult = {}
|
||||||
|
let curReq = obs.openCursor(
|
||||||
|
options.upperKey ? IDBKeyRange.bound(options.lowerKey, options.upperKey) : IDBKeyRange.lowerBound(options.lowerKey),
|
||||||
|
options.lastOnly ? "prev" : "next");
|
||||||
|
curReq.onsuccess = (evt) => {
|
||||||
|
var cursor = evt.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
if (options.patternEval(cursor.primaryKey, cursor.value)) {
|
||||||
|
filteredResult[cursor.primaryKey] = cursor.value;
|
||||||
|
options.lastOnly ? resolve(filteredResult) : cursor.continue();
|
||||||
|
} else
|
||||||
|
cursor.continue();
|
||||||
|
} else
|
||||||
|
resolve(filteredResult);
|
||||||
|
}
|
||||||
|
curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`);
|
||||||
|
db.close();
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})(window.compactIDB = {})
|
||||||
514
floBlockchainAPI.js
Normal file
514
floBlockchainAPI.js
Normal file
@ -0,0 +1,514 @@
|
|||||||
|
(function(EXPORTS) { //floBlockchainAPI v2.3.0
|
||||||
|
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
|
||||||
|
'use strict';
|
||||||
|
const floBlockchainAPI = EXPORTS;
|
||||||
|
|
||||||
|
const serverList = floGlobals.apiURL[floGlobals.blockchain].slice(0);
|
||||||
|
var curPos = floCrypto.randInt(0, serverList - 1);
|
||||||
|
|
||||||
|
function fetch_retry(apicall, rm_flosight) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let i = serverList.indexOf(rm_flosight)
|
||||||
|
if (i != -1) serverList.splice(i, 1);
|
||||||
|
curPos = floCrypto.randInt(0, serverList.length - 1);
|
||||||
|
fetch_api(apicall)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_api(apicall) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (serverList.length === 0)
|
||||||
|
reject("No floSight server working");
|
||||||
|
else {
|
||||||
|
let flosight = serverList[curPos];
|
||||||
|
fetch(flosight + apicall).then(response => {
|
||||||
|
if (response.ok)
|
||||||
|
response.json().then(data => resolve(data));
|
||||||
|
else {
|
||||||
|
fetch_retry(apicall, flosight)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
fetch_retry(apicall, flosight)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(floBlockchainAPI, 'current_server', {
|
||||||
|
get: () => serverList[curPos]
|
||||||
|
});
|
||||||
|
|
||||||
|
//Promised function to get data from API
|
||||||
|
const promisedAPI = floBlockchainAPI.promisedAPI = function(apicall) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
//console.log(apicall);
|
||||||
|
fetch_api(apicall)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get balance for the given Address
|
||||||
|
const getBalance = floBlockchainAPI.getBalance = function(addr) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
promisedAPI(`api/addr/${addr}/balance`)
|
||||||
|
.then(balance => resolve(parseFloat(balance)))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send Tx to blockchain
|
||||||
|
const sendTx = floBlockchainAPI.sendTx = function(senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!floCrypto.validateASCII(floData))
|
||||||
|
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
||||||
|
else if (!floCrypto.validateAddr(senderAddr))
|
||||||
|
return reject(`Invalid address : ${senderAddr}`);
|
||||||
|
else if (!floCrypto.validateAddr(receiverAddr))
|
||||||
|
return reject(`Invalid address : ${receiverAddr}`);
|
||||||
|
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
|
||||||
|
return reject("Invalid Private key!");
|
||||||
|
else if (typeof sendAmt !== 'number' || sendAmt <= 0)
|
||||||
|
return reject(`Invalid sendAmt : ${sendAmt}`);
|
||||||
|
|
||||||
|
//get unconfirmed tx list
|
||||||
|
promisedAPI(`api/addr/${senderAddr}`).then(result => {
|
||||||
|
readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => {
|
||||||
|
let unconfirmedSpent = {};
|
||||||
|
for (let tx of result.items)
|
||||||
|
if (tx.confirmations == 0)
|
||||||
|
for (let vin of tx.vin)
|
||||||
|
if (vin.addr === senderAddr) {
|
||||||
|
if (Array.isArray(unconfirmedSpent[vin.txid]))
|
||||||
|
unconfirmedSpent[vin.txid].push(vin.vout);
|
||||||
|
else
|
||||||
|
unconfirmedSpent[vin.txid] = [vin.vout];
|
||||||
|
}
|
||||||
|
//get utxos list
|
||||||
|
promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => {
|
||||||
|
//form/construct the transaction data
|
||||||
|
var trx = bitjs.transaction();
|
||||||
|
var utxoAmt = 0.0;
|
||||||
|
var fee = floGlobals.fee;
|
||||||
|
for (var i = utxos.length - 1;
|
||||||
|
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
|
||||||
|
//use only utxos with confirmations (strict_utxo mode)
|
||||||
|
if (utxos[i].confirmations || !strict_utxo) {
|
||||||
|
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
|
||||||
|
continue; //A transaction has already used the utxo, but is unconfirmed.
|
||||||
|
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||||
|
utxoAmt += utxos[i].amount;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (utxoAmt < sendAmt + fee)
|
||||||
|
reject("Insufficient FLO balance!");
|
||||||
|
else {
|
||||||
|
trx.addoutput(receiverAddr, sendAmt);
|
||||||
|
var change = utxoAmt - sendAmt - fee;
|
||||||
|
if (change > 0)
|
||||||
|
trx.addoutput(senderAddr, change);
|
||||||
|
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||||
|
var signedTxHash = trx.sign(privKey, 1);
|
||||||
|
broadcastTx(signedTxHash)
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write Data into blockchain
|
||||||
|
floBlockchainAPI.writeData = function(senderAddr, data, privKey, receiverAddr = floGlobals.adminID, strict_utxo = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (typeof data != "string")
|
||||||
|
data = JSON.stringify(data);
|
||||||
|
sendTx(senderAddr, receiverAddr, floGlobals.sendAmt, privKey, data, strict_utxo)
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//merge all UTXOs of a given floID into a single UTXO
|
||||||
|
floBlockchainAPI.mergeUTXOs = function(floID, privKey, floData = '') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!floCrypto.validateAddr(floID))
|
||||||
|
return reject(`Invalid floID`);
|
||||||
|
if (!floCrypto.verifyPrivKey(privKey, floID))
|
||||||
|
return reject("Invalid Private Key");
|
||||||
|
if (!floCrypto.validateASCII(floData))
|
||||||
|
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
||||||
|
var trx = bitjs.transaction();
|
||||||
|
var utxoAmt = 0.0;
|
||||||
|
var fee = floGlobals.fee;
|
||||||
|
promisedAPI(`api/addr/${floID}/utxo`).then(utxos => {
|
||||||
|
for (var i = utxos.length - 1; i >= 0; i--)
|
||||||
|
if (utxos[i].confirmations) {
|
||||||
|
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||||
|
utxoAmt += utxos[i].amount;
|
||||||
|
}
|
||||||
|
trx.addoutput(floID, utxoAmt - fee);
|
||||||
|
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||||
|
var signedTxHash = trx.sign(privKey, 1);
|
||||||
|
broadcastTx(signedTxHash)
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**Write data into blockchain from (and/or) to multiple floID
|
||||||
|
* @param {Array} senderPrivKeys List of sender private-keys
|
||||||
|
* @param {string} data FLO data of the txn
|
||||||
|
* @param {Array} receivers List of receivers
|
||||||
|
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
floBlockchainAPI.writeDataMultiple = function(senderPrivKeys, data, receivers = [floGlobals.adminID], preserveRatio = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!Array.isArray(senderPrivKeys))
|
||||||
|
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
|
||||||
|
if (!preserveRatio) {
|
||||||
|
let tmp = {};
|
||||||
|
let amount = (floGlobals.sendAmt * receivers.length) / senderPrivKeys.length;
|
||||||
|
senderPrivKeys.forEach(key => tmp[key] = amount);
|
||||||
|
senderPrivKeys = tmp;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(receivers))
|
||||||
|
return reject("Invalid receivers: Receivers must be Array");
|
||||||
|
else {
|
||||||
|
let tmp = {};
|
||||||
|
let amount = floGlobals.sendAmt;
|
||||||
|
receivers.forEach(floID => tmp[floID] = amount);
|
||||||
|
receivers = tmp
|
||||||
|
}
|
||||||
|
if (typeof data != "string")
|
||||||
|
data = JSON.stringify(data);
|
||||||
|
sendTxMultiple(senderPrivKeys, receivers, data)
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**Send Tx from (and/or) to multiple floID
|
||||||
|
* @param {Array or Object} senderPrivKeys List of sender private-key (optional: with coins to be sent)
|
||||||
|
* @param {Object} receivers List of receivers with respective amount to be sent
|
||||||
|
* @param {string} floData FLO data of the txn
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function(senderPrivKeys, receivers, floData = '') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!floCrypto.validateASCII(floData))
|
||||||
|
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
||||||
|
let senders = {},
|
||||||
|
preserveRatio;
|
||||||
|
//check for argument validations
|
||||||
|
try {
|
||||||
|
let invalids = {
|
||||||
|
InvalidSenderPrivKeys: [],
|
||||||
|
InvalidSenderAmountFor: [],
|
||||||
|
InvalidReceiverIDs: [],
|
||||||
|
InvalidReceiveAmountFor: []
|
||||||
|
}
|
||||||
|
let inputVal = 0,
|
||||||
|
outputVal = 0;
|
||||||
|
//Validate sender privatekeys (and send amount if passed)
|
||||||
|
//conversion when only privateKeys are passed (preserveRatio mode)
|
||||||
|
if (Array.isArray(senderPrivKeys)) {
|
||||||
|
senderPrivKeys.forEach(key => {
|
||||||
|
try {
|
||||||
|
if (!key)
|
||||||
|
invalids.InvalidSenderPrivKeys.push(key);
|
||||||
|
else {
|
||||||
|
let floID = floCrypto.getFloID(key);
|
||||||
|
senders[floID] = {
|
||||||
|
wif: key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
invalids.InvalidSenderPrivKeys.push(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
preserveRatio = true;
|
||||||
|
}
|
||||||
|
//conversion when privatekeys are passed with send amount
|
||||||
|
else {
|
||||||
|
for (let key in senderPrivKeys) {
|
||||||
|
try {
|
||||||
|
if (!key)
|
||||||
|
invalids.InvalidSenderPrivKeys.push(key);
|
||||||
|
else {
|
||||||
|
if (typeof senderPrivKeys[key] !== 'number' || senderPrivKeys[key] <= 0)
|
||||||
|
invalids.InvalidSenderAmountFor.push(key);
|
||||||
|
else
|
||||||
|
inputVal += senderPrivKeys[key];
|
||||||
|
let floID = floCrypto.getFloID(key);
|
||||||
|
senders[floID] = {
|
||||||
|
wif: key,
|
||||||
|
coins: senderPrivKeys[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
invalids.InvalidSenderPrivKeys.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preserveRatio = false;
|
||||||
|
}
|
||||||
|
//Validate the receiver IDs and receive amount
|
||||||
|
for (let floID in receivers) {
|
||||||
|
if (!floCrypto.validateAddr(floID))
|
||||||
|
invalids.InvalidReceiverIDs.push(floID);
|
||||||
|
if (typeof receivers[floID] !== 'number' || receivers[floID] <= 0)
|
||||||
|
invalids.InvalidReceiveAmountFor.push(floID);
|
||||||
|
else
|
||||||
|
outputVal += receivers[floID];
|
||||||
|
}
|
||||||
|
//Reject if any invalids are found
|
||||||
|
for (let i in invalids)
|
||||||
|
if (!invalids[i].length)
|
||||||
|
delete invalids[i];
|
||||||
|
if (Object.keys(invalids).length)
|
||||||
|
return reject(invalids);
|
||||||
|
//Reject if given inputVal and outputVal are not equal
|
||||||
|
if (!preserveRatio && inputVal != outputVal)
|
||||||
|
return reject(`Input Amount (${inputVal}) not equal to Output Amount (${outputVal})`);
|
||||||
|
} catch (error) {
|
||||||
|
return reject(error)
|
||||||
|
}
|
||||||
|
//Get balance of senders
|
||||||
|
let promises = [];
|
||||||
|
for (let floID in senders)
|
||||||
|
promises.push(getBalance(floID));
|
||||||
|
Promise.all(promises).then(results => {
|
||||||
|
let totalBalance = 0,
|
||||||
|
totalFee = floGlobals.fee,
|
||||||
|
balance = {};
|
||||||
|
//Divide fee among sender if not for preserveRatio
|
||||||
|
if (!preserveRatio)
|
||||||
|
var dividedFee = totalFee / Object.keys(senders).length;
|
||||||
|
//Check if balance of each sender is sufficient enough
|
||||||
|
let insufficient = [];
|
||||||
|
for (let floID in senders) {
|
||||||
|
balance[floID] = parseFloat(results.shift());
|
||||||
|
if (isNaN(balance[floID]) || (preserveRatio && balance[floID] <= totalFee) ||
|
||||||
|
(!preserveRatio && balance[floID] < senders[floID].coins + dividedFee))
|
||||||
|
insufficient.push(floID);
|
||||||
|
totalBalance += balance[floID];
|
||||||
|
}
|
||||||
|
if (insufficient.length)
|
||||||
|
return reject({
|
||||||
|
InsufficientBalance: insufficient
|
||||||
|
})
|
||||||
|
//Calculate totalSentAmount and check if totalBalance is sufficient
|
||||||
|
let totalSendAmt = totalFee;
|
||||||
|
for (floID in receivers)
|
||||||
|
totalSendAmt += receivers[floID];
|
||||||
|
if (totalBalance < totalSendAmt)
|
||||||
|
return reject("Insufficient total Balance");
|
||||||
|
//Get the UTXOs of the senders
|
||||||
|
let promises = [];
|
||||||
|
for (floID in senders)
|
||||||
|
promises.push(promisedAPI(`api/addr/${floID}/utxo`));
|
||||||
|
Promise.all(promises).then(results => {
|
||||||
|
let wifSeq = [];
|
||||||
|
var trx = bitjs.transaction();
|
||||||
|
for (floID in senders) {
|
||||||
|
let utxos = results.shift();
|
||||||
|
let sendAmt;
|
||||||
|
if (preserveRatio) {
|
||||||
|
let ratio = (balance[floID] / totalBalance);
|
||||||
|
sendAmt = totalSendAmt * ratio;
|
||||||
|
} else
|
||||||
|
sendAmt = senders[floID].coins + dividedFee;
|
||||||
|
let wif = senders[floID].wif;
|
||||||
|
let utxoAmt = 0.0;
|
||||||
|
for (let i = utxos.length - 1;
|
||||||
|
(i >= 0) && (utxoAmt < sendAmt); i--) {
|
||||||
|
if (utxos[i].confirmations) {
|
||||||
|
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||||
|
wifSeq.push(wif);
|
||||||
|
utxoAmt += utxos[i].amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (utxoAmt < sendAmt)
|
||||||
|
return reject("Insufficient balance:" + floID);
|
||||||
|
let change = (utxoAmt - sendAmt);
|
||||||
|
if (change > 0)
|
||||||
|
trx.addoutput(floID, change);
|
||||||
|
}
|
||||||
|
for (floID in receivers)
|
||||||
|
trx.addoutput(floID, receivers[floID]);
|
||||||
|
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||||
|
for (let i = 0; i < wifSeq.length; i++)
|
||||||
|
trx.signinput(i, wifSeq[i], 1);
|
||||||
|
var signedTxHash = trx.serialize();
|
||||||
|
broadcastTx(signedTxHash)
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Broadcast signed Tx in blockchain using API
|
||||||
|
const broadcastTx = floBlockchainAPI.broadcastTx = function(signedTxHash) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (signedTxHash.length < 1)
|
||||||
|
return reject("Empty Signature");
|
||||||
|
var url = serverList[curPos] + 'api/tx/send';
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: `{"rawtx":"${signedTxHash}"}`
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok)
|
||||||
|
response.json().then(data => resolve(data.txid.result));
|
||||||
|
else
|
||||||
|
response.text().then(data => resolve(data));
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
floBlockchainAPI.getTx = function(txid) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
promisedAPI(`api/tx/${txid}`)
|
||||||
|
.then(response => resolve(response))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read Txs of Address between from and to
|
||||||
|
const readTxs = floBlockchainAPI.readTxs = function(addr, from, to) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`)
|
||||||
|
.then(response => resolve(response))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read All Txs of Address (newest first)
|
||||||
|
floBlockchainAPI.readAllTxs = function(addr) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
||||||
|
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`)
|
||||||
|
.then(response => resolve(response.items))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Read flo Data from txs of given Address
|
||||||
|
options can be used to filter data
|
||||||
|
limit : maximum number of filtered data (default = 1000, negative = no limit)
|
||||||
|
ignoreOld : ignore old txs (default = 0)
|
||||||
|
sentOnly : filters only sent data
|
||||||
|
receivedOnly: filters only received data
|
||||||
|
pattern : filters data that with JSON pattern
|
||||||
|
filter : custom filter funtion for floData (eg . filter: d => {return d[0] == '$'})
|
||||||
|
tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details)
|
||||||
|
sender : flo-id(s) of sender
|
||||||
|
receiver : flo-id(s) of receiver
|
||||||
|
*/
|
||||||
|
floBlockchainAPI.readData = function(addr, options = {}) {
|
||||||
|
options.limit = options.limit || 0;
|
||||||
|
options.ignoreOld = options.ignoreOld || 0;
|
||||||
|
if (typeof options.sender === "string") options.sender = [options.sender];
|
||||||
|
if (typeof options.receiver === "string") options.receiver = [options.receiver];
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
||||||
|
var newItems = response.totalItems - options.ignoreOld;
|
||||||
|
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems*2}`).then(response => {
|
||||||
|
if (options.limit <= 0)
|
||||||
|
options.limit = response.items.length;
|
||||||
|
var filteredData = [];
|
||||||
|
let numToRead = response.totalItems - options.ignoreOld,
|
||||||
|
unconfirmedCount = 0;
|
||||||
|
for (let i = 0; i < numToRead && filteredData.length < options.limit; i++) {
|
||||||
|
if (!response.items[i].confirmations) { //unconfirmed transactions
|
||||||
|
unconfirmedCount++;
|
||||||
|
if (numToRead < response.items[i].length)
|
||||||
|
numToRead++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (options.pattern) {
|
||||||
|
try {
|
||||||
|
let jsonContent = JSON.parse(response.items[i].floData);
|
||||||
|
if (!Object.keys(jsonContent).includes(options.pattern))
|
||||||
|
continue;
|
||||||
|
} catch (error) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.sentOnly) {
|
||||||
|
let flag = false;
|
||||||
|
for (let vin of response.items[i].vin)
|
||||||
|
if (vin.addr === addr) {
|
||||||
|
flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!flag) continue;
|
||||||
|
}
|
||||||
|
if (Array.isArray(options.sender)) {
|
||||||
|
let flag = false;
|
||||||
|
for (let vin of response.items[i].vin)
|
||||||
|
if (options.sender.includes(vin.addr)) {
|
||||||
|
flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!flag) continue;
|
||||||
|
}
|
||||||
|
if (options.receivedOnly) {
|
||||||
|
let flag = false;
|
||||||
|
for (let vout of response.items[i].vout)
|
||||||
|
if (vout.scriptPubKey.addresses[0] === addr) {
|
||||||
|
flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!flag) continue;
|
||||||
|
}
|
||||||
|
if (Array.isArray(options.receiver)) {
|
||||||
|
let flag = false;
|
||||||
|
for (let vout of response.items[i].vout)
|
||||||
|
if (options.receiver.includes(vout.scriptPubKey.addresses[0])) {
|
||||||
|
flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!flag) continue;
|
||||||
|
}
|
||||||
|
if (options.filter && !options.filter(response.items[i].floData))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (options.tx) {
|
||||||
|
let d = {}
|
||||||
|
d.txid = response.items[i].txid;
|
||||||
|
d.time = response.items[i].time;
|
||||||
|
d.blockheight = response.items[i].blockheight;
|
||||||
|
d.data = response.items[i].floData;
|
||||||
|
filteredData.push(d);
|
||||||
|
} else
|
||||||
|
filteredData.push(response.items[i].floData);
|
||||||
|
}
|
||||||
|
resolve({
|
||||||
|
totalTxs: response.totalItems - unconfirmedCount,
|
||||||
|
data: filteredData
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})('object' === typeof module ? module.exports : window.floBlockchainAPI = {});
|
||||||
920
floCloudAPI.js
Normal file
920
floCloudAPI.js
Normal file
@ -0,0 +1,920 @@
|
|||||||
|
(function(EXPORTS) { //floCloudAPI v2.2.0a
|
||||||
|
/* FLO Cloud operations to send/request application data*/
|
||||||
|
'use strict';
|
||||||
|
const floCloudAPI = EXPORTS;
|
||||||
|
|
||||||
|
var kBucket;
|
||||||
|
const K_Bucket = floCloudAPI.K_Bucket = function(masterID, nodeList) {
|
||||||
|
|
||||||
|
const decodeID = floID => {
|
||||||
|
let k = bitjs.Base58.decode(floID);
|
||||||
|
k.shift();
|
||||||
|
k.splice(-4, 4);
|
||||||
|
let decodedId = Crypto.util.bytesToHex(k);
|
||||||
|
let nodeIdBigInt = new BigInteger(decodedId, 16);
|
||||||
|
let nodeIdBytes = nodeIdBigInt.toByteArrayUnsigned();
|
||||||
|
let nodeIdNewInt8Array = new Uint8Array(nodeIdBytes);
|
||||||
|
return nodeIdNewInt8Array;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _KB = new BuildKBucket({
|
||||||
|
localNodeId: decodeID(masterID)
|
||||||
|
});
|
||||||
|
nodeList.forEach(id => _KB.add({
|
||||||
|
id: decodeID(id),
|
||||||
|
floID: id
|
||||||
|
}));
|
||||||
|
|
||||||
|
const _CO = nodeList.map(id => [_KB.distance(_KB.localNodeId, decodeID(id)), id])
|
||||||
|
.sort((a, b) => a[0] - b[0])
|
||||||
|
.map(a => a[1]);
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
Object.defineProperty(self, 'tree', {
|
||||||
|
get: () => _KB
|
||||||
|
});
|
||||||
|
Object.defineProperty(self, 'list', {
|
||||||
|
get: () => Array.from(_CO)
|
||||||
|
});
|
||||||
|
|
||||||
|
self.isNode = floID => _CO.includes(floID);
|
||||||
|
self.innerNodes = function(id1, id2) {
|
||||||
|
if (!_CO.includes(id1) || !_CO.includes(id2))
|
||||||
|
throw Error('Given nodes are not supernode');
|
||||||
|
let iNodes = []
|
||||||
|
for (let i = _CO.indexOf(id1) + 1; _CO[i] != id2; i++) {
|
||||||
|
if (i < _CO.length)
|
||||||
|
iNodes.push(_CO[i])
|
||||||
|
else i = -1
|
||||||
|
}
|
||||||
|
return iNodes
|
||||||
|
}
|
||||||
|
self.outterNodes = function(id1, id2) {
|
||||||
|
if (!_CO.includes(id1) || !_CO.includes(id2))
|
||||||
|
throw Error('Given nodes are not supernode');
|
||||||
|
let oNodes = []
|
||||||
|
for (let i = _CO.indexOf(id2) + 1; _CO[i] != id1; i++) {
|
||||||
|
if (i < _CO.length)
|
||||||
|
oNodes.push(_CO[i])
|
||||||
|
else i = -1
|
||||||
|
}
|
||||||
|
return oNodes
|
||||||
|
}
|
||||||
|
self.prevNode = function(id, N = 1) {
|
||||||
|
let n = N || _CO.length;
|
||||||
|
if (!_CO.includes(id))
|
||||||
|
throw Error('Given node is not supernode');
|
||||||
|
let pNodes = []
|
||||||
|
for (let i = 0, j = _CO.indexOf(id) - 1; i < n; j--) {
|
||||||
|
if (j == _CO.indexOf(id))
|
||||||
|
break;
|
||||||
|
else if (j > -1)
|
||||||
|
pNodes[i++] = _CO[j]
|
||||||
|
else j = _CO.length
|
||||||
|
}
|
||||||
|
return (N == 1 ? pNodes[0] : pNodes)
|
||||||
|
}
|
||||||
|
self.nextNode = function(id, N = 1) {
|
||||||
|
let n = N || _CO.length;
|
||||||
|
if (!_CO.includes(id))
|
||||||
|
throw Error('Given node is not supernode');
|
||||||
|
if (!n) n = _CO.length;
|
||||||
|
let nNodes = []
|
||||||
|
for (let i = 0, j = _CO.indexOf(id) + 1; i < n; j++) {
|
||||||
|
if (j == _CO.indexOf(id))
|
||||||
|
break;
|
||||||
|
else if (j < _CO.length)
|
||||||
|
nNodes[i++] = _CO[j]
|
||||||
|
else j = -1
|
||||||
|
}
|
||||||
|
return (N == 1 ? nNodes[0] : nNodes)
|
||||||
|
}
|
||||||
|
self.closestNode = function(id, N = 1) {
|
||||||
|
let decodedId = decodeID(id);
|
||||||
|
let n = N || _CO.length;
|
||||||
|
let cNodes = _KB.closest(decodedId, n)
|
||||||
|
.map(k => k.floID)
|
||||||
|
return (N == 1 ? cNodes[0] : cNodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
floCloudAPI.init = function startCloudProcess(SNStorageID = floGlobals.SNStorageID, nodeList = null) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
nodeList = nodeList || Object.keys(floGlobals.supernodes);
|
||||||
|
kBucket = new K_Bucket(SNStorageID, nodeList);
|
||||||
|
resolve('Cloud init successful');
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(floCloudAPI, 'kBucket', {
|
||||||
|
get: () => kBucket
|
||||||
|
});
|
||||||
|
|
||||||
|
const _inactive = new Set();
|
||||||
|
|
||||||
|
function ws_connect(snID) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!(snID in floGlobals.supernodes))
|
||||||
|
return reject(`${snID} is not a supernode`)
|
||||||
|
if (_inactive.has(snID))
|
||||||
|
return reject(`${snID} is not active`)
|
||||||
|
var wsConn = new WebSocket("wss://" + floGlobals.supernodes[snID].uri + "/");
|
||||||
|
wsConn.onopen = evt => resolve(wsConn);
|
||||||
|
wsConn.onerror = evt => {
|
||||||
|
_inactive.add(snID)
|
||||||
|
reject(`${snID} is unavailable`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ws_activeConnect(snID, reverse = false) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (_inactive.size === kBucket.list.length)
|
||||||
|
return reject('Cloud offline');
|
||||||
|
if (!(snID in floGlobals.supernodes))
|
||||||
|
snID = kBucket.closestNode(snID);
|
||||||
|
ws_connect(snID)
|
||||||
|
.then(node => resolve(node))
|
||||||
|
.catch(error => {
|
||||||
|
if (reverse)
|
||||||
|
var nxtNode = kBucket.prevNode(snID);
|
||||||
|
else
|
||||||
|
var nxtNode = kBucket.nextNode(snID);
|
||||||
|
ws_activeConnect(nxtNode, reverse)
|
||||||
|
.then(node => resolve(node))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_API(snID, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (_inactive.has(snID))
|
||||||
|
return reject(`${snID} is not active`);
|
||||||
|
let fetcher, sn_url = "https://" + floGlobals.supernodes[snID].uri;
|
||||||
|
if (typeof data === "string")
|
||||||
|
fetcher = fetch(sn_url + "?" + data);
|
||||||
|
else if (typeof data === "object" && data.method === "POST")
|
||||||
|
fetcher = fetch(sn_url, data);
|
||||||
|
fetcher.then(response => {
|
||||||
|
if (response.ok || response.status === 400 || response.status === 500)
|
||||||
|
resolve(response);
|
||||||
|
else
|
||||||
|
reject(response);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_ActiveAPI(snID, data, reverse = false) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (_inactive.size === kBucket.list.length)
|
||||||
|
return reject('Cloud offline');
|
||||||
|
if (!(snID in floGlobals.supernodes))
|
||||||
|
snID = kBucket.closestNode(snID);
|
||||||
|
fetch_API(snID, data)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => {
|
||||||
|
_inactive.add(snID)
|
||||||
|
if (reverse)
|
||||||
|
var nxtNode = kBucket.prevNode(snID);
|
||||||
|
else
|
||||||
|
var nxtNode = kBucket.nextNode(snID);
|
||||||
|
fetch_ActiveAPI(nxtNode, data, reverse)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function singleRequest(floID, data_obj, method = "POST") {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let data;
|
||||||
|
if (method === "POST")
|
||||||
|
data = {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data_obj)
|
||||||
|
};
|
||||||
|
else
|
||||||
|
data = new URLSearchParams(JSON.parse(JSON.stringify(data_obj))).toString();
|
||||||
|
fetch_ActiveAPI(floID, data).then(response => {
|
||||||
|
if (response.ok)
|
||||||
|
response.json()
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
else response.text()
|
||||||
|
.then(result => reject(response.status + ": " + result)) //Error Message from Node
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const _liveRequest = {};
|
||||||
|
|
||||||
|
function liveRequest(floID, request, callback) {
|
||||||
|
const filterData = typeof request.status !== 'undefined' ?
|
||||||
|
data => {
|
||||||
|
if (request.status)
|
||||||
|
return data;
|
||||||
|
else {
|
||||||
|
let filtered = {};
|
||||||
|
for (let i in data)
|
||||||
|
if (request.trackList.includes(i))
|
||||||
|
filtered[i] = data[i];
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
} :
|
||||||
|
data => {
|
||||||
|
data = objectifier(data);
|
||||||
|
let filtered = {},
|
||||||
|
r = request;
|
||||||
|
for (let v in data) {
|
||||||
|
let d = data[v];
|
||||||
|
if ((!r.atVectorClock || r.atVectorClock == v) &&
|
||||||
|
(r.atVectorClock || !r.lowerVectorClock || r.lowerVectorClock <= v) &&
|
||||||
|
(r.atVectorClock || !r.upperVectorClock || r.upperVectorClock >= v) &&
|
||||||
|
(!r.afterTime || r.afterTime < d.log_time) &&
|
||||||
|
r.application == d.application &&
|
||||||
|
r.receiverID == d.receiverID &&
|
||||||
|
(!r.comment || r.comment == d.comment) &&
|
||||||
|
(!r.type || r.type == d.type) &&
|
||||||
|
(!r.senderID || r.senderID.includes(d.senderID)))
|
||||||
|
filtered[v] = data[v];
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ws_activeConnect(floID).then(node => {
|
||||||
|
let randID = floCrypto.randString(5);
|
||||||
|
node.send(JSON.stringify(request));
|
||||||
|
node.onmessage = (evt) => {
|
||||||
|
let d = null,
|
||||||
|
e = null;
|
||||||
|
try {
|
||||||
|
d = filterData(JSON.parse(evt.data));
|
||||||
|
} catch (error) {
|
||||||
|
e = evt.data
|
||||||
|
} finally {
|
||||||
|
callback(d, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_liveRequest[randID] = node;
|
||||||
|
_liveRequest[randID].request = request;
|
||||||
|
resolve(randID);
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(floCloudAPI, 'liveRequest', {
|
||||||
|
get: () => _liveRequest
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(floCloudAPI, 'inactive', {
|
||||||
|
get: () => _inactive
|
||||||
|
});
|
||||||
|
|
||||||
|
const util = floCloudAPI.util = {};
|
||||||
|
|
||||||
|
const encodeMessage = util.encodeMessage = function(message) {
|
||||||
|
return btoa(unescape(encodeURIComponent(JSON.stringify(message))))
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodeMessage = util.decodeMessage = function(message) {
|
||||||
|
return JSON.parse(decodeURIComponent(escape(atob(message))))
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterKey = util.filterKey = function(type, options) {
|
||||||
|
return type + (options.comment ? ':' + options.comment : '') +
|
||||||
|
'|' + (options.group || options.receiverID || floGlobals.adminID) +
|
||||||
|
'|' + (options.application || floGlobals.application);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastCommit = {};
|
||||||
|
Object.defineProperty(lastCommit, 'get', {
|
||||||
|
value: objName => JSON.parse(lastCommit[objName])
|
||||||
|
});
|
||||||
|
Object.defineProperty(lastCommit, 'set', {
|
||||||
|
value: objName => lastCommit[objName] = JSON.stringify(floGlobals.appObjects[objName])
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateObject(objectName, dataSet) {
|
||||||
|
try {
|
||||||
|
console.log(dataSet)
|
||||||
|
let vcList = Object.keys(dataSet).sort();
|
||||||
|
for (let vc of vcList) {
|
||||||
|
if (vc < floGlobals.lastVC[objectName] || dataSet[vc].type !== objectName)
|
||||||
|
continue;
|
||||||
|
switch (dataSet[vc].comment) {
|
||||||
|
case "RESET":
|
||||||
|
if (dataSet[vc].message.reset)
|
||||||
|
floGlobals.appObjects[objectName] = dataSet[vc].message.reset;
|
||||||
|
break;
|
||||||
|
case "UPDATE":
|
||||||
|
if (dataSet[vc].message.diff)
|
||||||
|
floGlobals.appObjects[objectName] = diff.merge(floGlobals.appObjects[objectName], dataSet[vc].message.diff);
|
||||||
|
}
|
||||||
|
floGlobals.lastVC[objectName] = vc;
|
||||||
|
}
|
||||||
|
lastCommit.set(objectName);
|
||||||
|
compactIDB.writeData("appObjects", floGlobals.appObjects[objectName], objectName);
|
||||||
|
compactIDB.writeData("lastVC", floGlobals.lastVC[objectName], objectName);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeGeneral(fk, dataSet) {
|
||||||
|
try {
|
||||||
|
console.log(dataSet)
|
||||||
|
if (typeof floGlobals.generalData[fk] !== "object")
|
||||||
|
floGlobals.generalData[fk] = {}
|
||||||
|
for (let vc in dataSet) {
|
||||||
|
floGlobals.generalData[fk][vc] = dataSet[vc];
|
||||||
|
if (dataSet[vc].log_time > floGlobals.lastVC[fk])
|
||||||
|
floGlobals.lastVC[fk] = dataSet[vc].log_time;
|
||||||
|
}
|
||||||
|
compactIDB.writeData("lastVC", floGlobals.lastVC[fk], fk)
|
||||||
|
compactIDB.writeData("generalData", floGlobals.generalData[fk], fk)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function objectifier(data) {
|
||||||
|
if (!Array.isArray(data))
|
||||||
|
data = [data];
|
||||||
|
return Object.fromEntries(data.map(d => {
|
||||||
|
d.message = decodeMessage(d.message);
|
||||||
|
return [d.vectorClock, d];
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
//set status as online for myFloID
|
||||||
|
floCloudAPI.setStatus = function(options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let callback = options.callback instanceof Function ? options.callback : (d, e) => console.debug(d, e);
|
||||||
|
var request = {
|
||||||
|
floID: myFloID,
|
||||||
|
application: options.application || floGlobals.application,
|
||||||
|
time: Date.now(),
|
||||||
|
status: true,
|
||||||
|
pubKey: myPubKey
|
||||||
|
}
|
||||||
|
let hashcontent = ["time", "application", "floID"].map(d => request[d]).join("|");
|
||||||
|
request.sign = floCrypto.signData(hashcontent, myPrivKey);
|
||||||
|
liveRequest(options.refID || floGlobals.adminID, request, callback)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//request status of floID(s) in trackList
|
||||||
|
floCloudAPI.requestStatus = function(trackList, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!Array.isArray(trackList))
|
||||||
|
trackList = [trackList];
|
||||||
|
let callback = options.callback instanceof Function ? options.callback : (d, e) => console.debug(d, e);
|
||||||
|
let request = {
|
||||||
|
status: false,
|
||||||
|
application: options.application || floGlobals.application,
|
||||||
|
trackList: trackList
|
||||||
|
}
|
||||||
|
liveRequest(options.refID || floGlobals.adminID, request, callback)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//send any message to supernode cloud storage
|
||||||
|
const sendApplicationData = floCloudAPI.sendApplicationData = function(message, type, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var data = {
|
||||||
|
senderID: myFloID,
|
||||||
|
receiverID: options.receiverID || floGlobals.adminID,
|
||||||
|
pubKey: myPubKey,
|
||||||
|
message: encodeMessage(message),
|
||||||
|
time: Date.now(),
|
||||||
|
application: options.application || floGlobals.application,
|
||||||
|
type: type,
|
||||||
|
comment: options.comment || ""
|
||||||
|
}
|
||||||
|
let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"]
|
||||||
|
.map(d => data[d]).join("|")
|
||||||
|
data.sign = floCrypto.signData(hashcontent, myPrivKey);
|
||||||
|
singleRequest(data.receiverID, data)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//request any data from supernode cloud
|
||||||
|
const requestApplicationData = floCloudAPI.requestApplicationData = function(type, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var request = {
|
||||||
|
receiverID: options.receiverID || floGlobals.adminID,
|
||||||
|
senderID: options.senderID || undefined,
|
||||||
|
application: options.application || floGlobals.application,
|
||||||
|
type: type,
|
||||||
|
comment: options.comment || undefined,
|
||||||
|
lowerVectorClock: options.lowerVectorClock || undefined,
|
||||||
|
upperVectorClock: options.upperVectorClock || undefined,
|
||||||
|
atVectorClock: options.atVectorClock || undefined,
|
||||||
|
afterTime: options.afterTime || undefined,
|
||||||
|
mostRecent: options.mostRecent || undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.callback instanceof Function) {
|
||||||
|
liveRequest(request.receiverID, request, options.callback)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
} else {
|
||||||
|
if (options.method === "POST")
|
||||||
|
request = {
|
||||||
|
time: Date.now(),
|
||||||
|
request
|
||||||
|
};
|
||||||
|
singleRequest(request.receiverID, request, options.method || "GET")
|
||||||
|
.then(data => resolve(data)).catch(error => reject(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//(NEEDS UPDATE) delete data from supernode cloud (received only)
|
||||||
|
floCloudAPI.deleteApplicationData = function(vectorClocks, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var delreq = {
|
||||||
|
requestorID: myFloID,
|
||||||
|
pubKey: myPubKey,
|
||||||
|
time: Date.now(),
|
||||||
|
delete: (Array.isArray(vectorClocks) ? vectorClocks : [vectorClocks]),
|
||||||
|
application: options.application || floGlobals.application
|
||||||
|
}
|
||||||
|
let hashcontent = ["time", "application", "delete"]
|
||||||
|
.map(d => delreq[d]).join("|")
|
||||||
|
delreq.sign = floCrypto.signData(hashcontent, myPrivKey)
|
||||||
|
singleRequest(delreq.requestorID, delreq).then(result => {
|
||||||
|
let success = [],
|
||||||
|
failed = [];
|
||||||
|
result.forEach(r => r.status === 'fulfilled' ?
|
||||||
|
success.push(r.value) : failed.push(r.reason));
|
||||||
|
resolve({
|
||||||
|
success,
|
||||||
|
failed
|
||||||
|
})
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//(NEEDS UPDATE) edit comment of data in supernode cloud (mutable comments only)
|
||||||
|
floCloudAPI.editApplicationData = function(vectorClock, newComment, oldData, 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 != myFloID)
|
||||||
|
return reject("Invalid requestorID")
|
||||||
|
else if (!d.comment.startsWith("EDIT:"))
|
||||||
|
return reject("Data immutable")
|
||||||
|
let data = {
|
||||||
|
requestorID: myFloID,
|
||||||
|
receiverID: d.receiverID,
|
||||||
|
time: Date.now(),
|
||||||
|
application: d.application,
|
||||||
|
edit: {
|
||||||
|
vectorClock: vectorClock,
|
||||||
|
comment: newComment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.comment = data.edit.comment;
|
||||||
|
let hashcontent = ["receiverID", "time", "application", "type", "message",
|
||||||
|
"comment"
|
||||||
|
]
|
||||||
|
.map(x => d[x]).join("|")
|
||||||
|
data.edit.sign = floCrypto.signData(hashcontent, myPrivKey)
|
||||||
|
singleRequest(data.receiverID, data)
|
||||||
|
.then(result => resolve("Data comment updated"))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag data in supernode cloud (subAdmin access only)
|
||||||
|
floCloudAPI.tagApplicationData = function(vectorClock, tag, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!floGlobals.subAdmins.includes(myFloID))
|
||||||
|
return reject("Only subAdmins can tag data")
|
||||||
|
var request = {
|
||||||
|
receiverID: options.receiverID || floGlobals.adminID,
|
||||||
|
requestorID: myFloID,
|
||||||
|
pubKey: myPubKey,
|
||||||
|
time: Date.now(),
|
||||||
|
vectorClock: vectorClock,
|
||||||
|
tag: tag,
|
||||||
|
}
|
||||||
|
let hashcontent = ["time", "vectorClock", 'tag'].map(d => request[d]).join("|");
|
||||||
|
request.sign = floCrypto.signData(hashcontent, myPrivKey);
|
||||||
|
singleRequest(request.receiverID, request)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//note data in supernode cloud (receiver only or subAdmin allowed if receiver is adminID)
|
||||||
|
floCloudAPI.noteApplicationData = function(vectorClock, note, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var request = {
|
||||||
|
receiverID: options.receiverID || floGlobals.adminID,
|
||||||
|
requestorID: myFloID,
|
||||||
|
pubKey: myPubKey,
|
||||||
|
time: Date.now(),
|
||||||
|
vectorClock: vectorClock,
|
||||||
|
note: note,
|
||||||
|
}
|
||||||
|
let hashcontent = ["time", "vectorClock", 'note'].map(d => request[d]).join("|");
|
||||||
|
request.sign = floCrypto.signData(hashcontent, myPrivKey);
|
||||||
|
singleRequest(request.receiverID, request)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//send general data
|
||||||
|
floCloudAPI.sendGeneralData = function(message, type, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (options.encrypt) {
|
||||||
|
let encryptionKey = options.encrypt === true ?
|
||||||
|
floGlobals.settings.encryptionKey : options.encrypt
|
||||||
|
message = floCrypto.encryptData(JSON.stringify(message), encryptionKey)
|
||||||
|
}
|
||||||
|
sendApplicationData(message, type, options)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//request general data
|
||||||
|
floCloudAPI.requestGeneralData = function(type, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var fk = filterKey(type, options)
|
||||||
|
floGlobals.lastVC[fk] = parseInt(floGlobals.lastVC[fk]) || 0;
|
||||||
|
options.afterTime = options.afterTime || floGlobals.lastVC[fk];
|
||||||
|
if (options.callback instanceof Function) {
|
||||||
|
let new_options = Object.create(options)
|
||||||
|
new_options.callback = (d, e) => {
|
||||||
|
storeGeneral(fk, d);
|
||||||
|
options.callback(d, e)
|
||||||
|
}
|
||||||
|
requestApplicationData(type, new_options)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
} else {
|
||||||
|
requestApplicationData(type, options).then(dataSet => {
|
||||||
|
storeGeneral(fk, objectifier(dataSet))
|
||||||
|
resolve(dataSet)
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//request an object data from supernode cloud
|
||||||
|
floCloudAPI.requestObjectData = function(objectName, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
options.lowerVectorClock = options.lowerVectorClock || floGlobals.lastVC[objectName] + 1;
|
||||||
|
options.senderID = [false, null].includes(options.senderID) ? null :
|
||||||
|
options.senderID || floGlobals.subAdmins;
|
||||||
|
options.mostRecent = true;
|
||||||
|
options.comment = 'RESET';
|
||||||
|
let callback = null;
|
||||||
|
if (options.callback instanceof Function) {
|
||||||
|
let old_callback = options.callback;
|
||||||
|
callback = (d, e) => {
|
||||||
|
updateObject(objectName, d);
|
||||||
|
old_callback(d, e);
|
||||||
|
}
|
||||||
|
delete options.callback;
|
||||||
|
}
|
||||||
|
requestApplicationData(objectName, options).then(dataSet => {
|
||||||
|
updateObject(objectName, objectifier(dataSet));
|
||||||
|
delete options.comment;
|
||||||
|
options.lowerVectorClock = floGlobals.lastVC[objectName] + 1;
|
||||||
|
delete options.mostRecent;
|
||||||
|
if (callback) {
|
||||||
|
let new_options = Object.create(options);
|
||||||
|
new_options.callback = callback;
|
||||||
|
requestApplicationData(objectName, new_options)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
} else {
|
||||||
|
requestApplicationData(objectName, options).then(dataSet => {
|
||||||
|
updateObject(objectName, objectifier(dataSet))
|
||||||
|
resolve(floGlobals.appObjects[objectName])
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
floCloudAPI.closeRequest = function(requestID) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let conn = _liveRequest[requestID]
|
||||||
|
if (!conn)
|
||||||
|
return reject('Request not found')
|
||||||
|
conn.onclose = evt => {
|
||||||
|
delete _liveRequest[requestID];
|
||||||
|
resolve('Request connection closed')
|
||||||
|
}
|
||||||
|
conn.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//reset or initialize an object and send it to cloud
|
||||||
|
floCloudAPI.resetObjectData = function(objectName, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let message = {
|
||||||
|
reset: floGlobals.appObjects[objectName]
|
||||||
|
}
|
||||||
|
options.comment = 'RESET';
|
||||||
|
sendApplicationData(message, objectName, options).then(result => {
|
||||||
|
lastCommit.set(objectName);
|
||||||
|
resolve(result)
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//update the diff and send it to cloud
|
||||||
|
floCloudAPI.updateObjectData = function(objectName, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let message = {
|
||||||
|
diff: diff.find(lastCommit.get(objectName), floGlobals.appObjects[
|
||||||
|
objectName])
|
||||||
|
}
|
||||||
|
options.comment = 'UPDATE';
|
||||||
|
sendApplicationData(message, objectName, options).then(result => {
|
||||||
|
lastCommit.set(objectName);
|
||||||
|
resolve(result)
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Functions:
|
||||||
|
findDiff(original, updatedObj) returns an object with the added, deleted and updated differences
|
||||||
|
mergeDiff(original, allDiff) returns a new object from original object merged with all differences (allDiff is returned object of findDiff)
|
||||||
|
*/
|
||||||
|
var diff = (function() {
|
||||||
|
const isDate = d => d instanceof Date;
|
||||||
|
const isEmpty = o => Object.keys(o).length === 0;
|
||||||
|
const isObject = o => o != null && typeof o === 'object';
|
||||||
|
const properObject = o => isObject(o) && !o.hasOwnProperty ? {
|
||||||
|
...o
|
||||||
|
} : o;
|
||||||
|
const getLargerArray = (l, r) => l.length > r.length ? l : r;
|
||||||
|
|
||||||
|
const preserve = (diff, left, right) => {
|
||||||
|
if (!isObject(diff)) return diff;
|
||||||
|
return Object.keys(diff).reduce((acc, key) => {
|
||||||
|
const leftArray = left[key];
|
||||||
|
const rightArray = right[key];
|
||||||
|
if (Array.isArray(leftArray) && Array.isArray(rightArray)) {
|
||||||
|
const array = [...getLargerArray(leftArray, rightArray)];
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: array.reduce((acc2, item, index) => {
|
||||||
|
if (diff[key].hasOwnProperty(index)) {
|
||||||
|
acc2[index] = preserve(diff[key][index], leftArray[index], rightArray[index]); // diff recurse and check for nested arrays
|
||||||
|
return acc2;
|
||||||
|
}
|
||||||
|
delete acc2[index]; // no diff aka empty
|
||||||
|
return acc2;
|
||||||
|
}, array)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: diff[key]
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedDiff = (lhs, rhs) => {
|
||||||
|
if (lhs === rhs) return {};
|
||||||
|
if (!isObject(lhs) || !isObject(rhs)) return rhs;
|
||||||
|
const l = properObject(lhs);
|
||||||
|
const r = properObject(rhs);
|
||||||
|
if (isDate(l) || isDate(r)) {
|
||||||
|
if (l.valueOf() == r.valueOf()) return {};
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
return Object.keys(r).reduce((acc, key) => {
|
||||||
|
if (l.hasOwnProperty(key)) {
|
||||||
|
const difference = updatedDiff(l[key], r[key]);
|
||||||
|
if (isObject(difference) && isEmpty(difference) && !isDate(difference)) return acc;
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: difference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const diff = (lhs, rhs) => {
|
||||||
|
if (lhs === rhs) return {}; // equal return no diff
|
||||||
|
if (!isObject(lhs) || !isObject(rhs)) return rhs; // return updated rhs
|
||||||
|
const l = properObject(lhs);
|
||||||
|
const r = properObject(rhs);
|
||||||
|
const deletedValues = Object.keys(l).reduce((acc, key) => {
|
||||||
|
return r.hasOwnProperty(key) ? acc : {
|
||||||
|
...acc,
|
||||||
|
[key]: null
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
if (isDate(l) || isDate(r)) {
|
||||||
|
if (l.valueOf() == r.valueOf()) return {};
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
return Object.keys(r).reduce((acc, key) => {
|
||||||
|
if (!l.hasOwnProperty(key)) return {
|
||||||
|
...acc,
|
||||||
|
[key]: r[key]
|
||||||
|
}; // return added r key
|
||||||
|
const difference = diff(l[key], r[key]);
|
||||||
|
if (isObject(difference) && isEmpty(difference) && !isDate(difference)) return acc; // return no diff
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: difference
|
||||||
|
}; // return updated key
|
||||||
|
}, deletedValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addedDiff = (lhs, rhs) => {
|
||||||
|
if (lhs === rhs || !isObject(lhs) || !isObject(rhs)) return {};
|
||||||
|
const l = properObject(lhs);
|
||||||
|
const r = properObject(rhs);
|
||||||
|
return Object.keys(r).reduce((acc, key) => {
|
||||||
|
if (l.hasOwnProperty(key)) {
|
||||||
|
const difference = addedDiff(l[key], r[key]);
|
||||||
|
if (isObject(difference) && isEmpty(difference)) return acc;
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: difference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: r[key]
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const arrayDiff = (lhs, rhs) => {
|
||||||
|
if (lhs === rhs) return {}; // equal return no diff
|
||||||
|
if (!isObject(lhs) || !isObject(rhs)) return rhs; // return updated rhs
|
||||||
|
const l = properObject(lhs);
|
||||||
|
const r = properObject(rhs);
|
||||||
|
const deletedValues = Object.keys(l).reduce((acc, key) => {
|
||||||
|
return r.hasOwnProperty(key) ? acc : {
|
||||||
|
...acc,
|
||||||
|
[key]: null
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
if (isDate(l) || isDate(r)) {
|
||||||
|
if (l.valueOf() == r.valueOf()) return {};
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if (Array.isArray(r) && Array.isArray(l)) {
|
||||||
|
const deletedValues = l.reduce((acc, item, index) => {
|
||||||
|
return r.hasOwnProperty(index) ? acc.concat(item) : acc.concat(null);
|
||||||
|
}, []);
|
||||||
|
return r.reduce((acc, rightItem, index) => {
|
||||||
|
if (!deletedValues.hasOwnProperty(index)) {
|
||||||
|
return acc.concat(rightItem);
|
||||||
|
}
|
||||||
|
const leftItem = l[index];
|
||||||
|
const difference = diff(rightItem, leftItem);
|
||||||
|
if (isObject(difference) && isEmpty(difference) && !isDate(difference)) {
|
||||||
|
delete acc[index];
|
||||||
|
return acc; // return no diff
|
||||||
|
}
|
||||||
|
return acc.slice(0, index).concat(rightItem).concat(acc.slice(index + 1)); // return updated key
|
||||||
|
}, deletedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(r).reduce((acc, key) => {
|
||||||
|
if (!l.hasOwnProperty(key)) return {
|
||||||
|
...acc,
|
||||||
|
[key]: r[key]
|
||||||
|
}; // return added r key
|
||||||
|
const difference = diff(l[key], r[key]);
|
||||||
|
if (isObject(difference) && isEmpty(difference) && !isDate(difference)) return acc; // return no diff
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: difference
|
||||||
|
}; // return updated key
|
||||||
|
}, deletedValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletedDiff = (lhs, rhs) => {
|
||||||
|
if (lhs === rhs || !isObject(lhs) || !isObject(rhs)) return {};
|
||||||
|
const l = properObject(lhs);
|
||||||
|
const r = properObject(rhs);
|
||||||
|
return Object.keys(l).reduce((acc, key) => {
|
||||||
|
if (r.hasOwnProperty(key)) {
|
||||||
|
const difference = deletedDiff(l[key], r[key]);
|
||||||
|
if (isObject(difference) && isEmpty(difference)) return acc;
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: difference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: null
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeRecursive = (obj1, obj2) => {
|
||||||
|
for (var p in obj2) {
|
||||||
|
try {
|
||||||
|
if (obj2[p].constructor == Object)
|
||||||
|
obj1[p] = mergeRecursive(obj1[p], obj2[p]);
|
||||||
|
// Property in destination object set; update its value.
|
||||||
|
else if (Ext.isArray(obj2[p])) {
|
||||||
|
// obj1[p] = [];
|
||||||
|
if (obj2[p].length < 1)
|
||||||
|
obj1[p] = obj2[p];
|
||||||
|
else
|
||||||
|
obj1[p] = mergeRecursive(obj1[p], obj2[p]);
|
||||||
|
} else
|
||||||
|
obj1[p] = obj2[p];
|
||||||
|
} catch (e) {
|
||||||
|
// Property in destination object not set; create it and set its value.
|
||||||
|
obj1[p] = obj2[p];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanse = (obj) => {
|
||||||
|
Object.keys(obj).forEach(key => {
|
||||||
|
var value = obj[key];
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
// Recurse...
|
||||||
|
cleanse(value);
|
||||||
|
// ...and remove if now "empty" (NOTE: insert your definition of "empty" here)
|
||||||
|
//if (!Object.keys(value).length)
|
||||||
|
// delete obj[key];
|
||||||
|
} else if (value === null)
|
||||||
|
delete obj[key]; // null, remove it
|
||||||
|
});
|
||||||
|
if (obj.constructor.toString().indexOf("Array") != -1) {
|
||||||
|
obj = obj.filter(function(el) {
|
||||||
|
return el != null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const findDiff = (lhs, rhs) => ({
|
||||||
|
added: addedDiff(lhs, rhs),
|
||||||
|
deleted: deletedDiff(lhs, rhs),
|
||||||
|
updated: updatedDiff(lhs, rhs),
|
||||||
|
});
|
||||||
|
|
||||||
|
/*obj is original object or array, diff is the output of findDiff */
|
||||||
|
const mergeDiff = (obj, diff) => {
|
||||||
|
if (Object.keys(diff.updated).length !== 0)
|
||||||
|
obj = mergeRecursive(obj, diff.updated)
|
||||||
|
if (Object.keys(diff.deleted).length !== 0) {
|
||||||
|
obj = mergeRecursive(obj, diff.deleted)
|
||||||
|
obj = cleanse(obj)
|
||||||
|
}
|
||||||
|
if (Object.keys(diff.added).length !== 0)
|
||||||
|
obj = mergeRecursive(obj, diff.added)
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
find: findDiff,
|
||||||
|
merge: mergeDiff
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
})('object' === typeof module ? module.exports : window.floCloudAPI = {})
|
||||||
313
floCrypto.js
Normal file
313
floCrypto.js
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
(function(EXPORTS) { //floCrypto v2.3.0a
|
||||||
|
/* FLO Crypto Operators */
|
||||||
|
'use strict';
|
||||||
|
const floCrypto = EXPORTS;
|
||||||
|
|
||||||
|
const p = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
|
||||||
|
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
||||||
|
const ascii_alternatives = `‘ '\n’ '\n“ "\n” "\n– --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
|
||||||
|
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
|
||||||
|
|
||||||
|
function calculateY(x) {
|
||||||
|
let exp = exponent1();
|
||||||
|
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
|
||||||
|
return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUncompressedPublicKey(compressedPublicKey) {
|
||||||
|
// Fetch x from compressedPublicKey
|
||||||
|
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
|
||||||
|
const prefix = pubKeyBytes.shift() // remove prefix
|
||||||
|
let prefix_modulus = prefix % 2;
|
||||||
|
pubKeyBytes.unshift(0) // add prefix 0
|
||||||
|
let x = new BigInteger(pubKeyBytes)
|
||||||
|
let xDecimalValue = x.toString()
|
||||||
|
// Fetch y
|
||||||
|
let y = calculateY(x);
|
||||||
|
let yDecimalValue = y.toString();
|
||||||
|
// verify y value
|
||||||
|
let resultBigInt = y.mod(BigInteger("2"));
|
||||||
|
let check = resultBigInt.toString() % 2;
|
||||||
|
if (prefix_modulus !== check)
|
||||||
|
yDecimalValue = y.negate().mod(p).toString();
|
||||||
|
return {
|
||||||
|
x: xDecimalValue,
|
||||||
|
y: yDecimalValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSenderPublicKeyString() {
|
||||||
|
let privateKey = ellipticCurveEncryption.senderRandom();
|
||||||
|
var senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
|
||||||
|
return {
|
||||||
|
privateKey: privateKey,
|
||||||
|
senderPublicKeyString: senderPublicKeyString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
|
||||||
|
let receiverPublicKeyString = getUncompressedPublicKey(receiverPublicKeyHex);
|
||||||
|
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
|
||||||
|
receiverPublicKeyString.x, receiverPublicKeyString.y, senderPrivateKey);
|
||||||
|
return senderDerivedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
|
||||||
|
return ellipticCurveEncryption.receiverSharedKeyDerivation(
|
||||||
|
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReceiverPublicKeyString(privateKey) {
|
||||||
|
return ellipticCurveEncryption.receiverPublicString(privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
|
||||||
|
let pk = Bitcoin.Base58.decode(pk_wif)
|
||||||
|
pk.shift()
|
||||||
|
pk.splice(-4, 4)
|
||||||
|
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
|
||||||
|
if (isPubKeyCompressed == true) pk.pop()
|
||||||
|
pk.unshift(0)
|
||||||
|
let privateKeyDecimal = BigInteger(pk).toString()
|
||||||
|
let privateKeyHex = Crypto.util.bytesToHex(pk)
|
||||||
|
return {
|
||||||
|
privateKeyDecimal: privateKeyDecimal,
|
||||||
|
privateKeyHex: privateKeyHex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//generate a random Interger within range
|
||||||
|
floCrypto.randInt = function(min, max) {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
//generate a random String within length (options : alphaNumeric chars only)
|
||||||
|
floCrypto.randString = function(length, alphaNumeric = true) {
|
||||||
|
var result = '';
|
||||||
|
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Encrypt Data using public-key
|
||||||
|
floCrypto.encryptData = function(data, receiverPublicKeyHex) {
|
||||||
|
var senderECKeyData = getSenderPublicKeyString();
|
||||||
|
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
|
||||||
|
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
||||||
|
let secret = Crypto.AES.encrypt(data, senderKey);
|
||||||
|
return {
|
||||||
|
secret: secret,
|
||||||
|
senderPublicKeyString: senderECKeyData.senderPublicKeyString
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//Decrypt Data using private-key
|
||||||
|
floCrypto.decryptData = function(data, privateKeyHex) {
|
||||||
|
var receiverECKeyData = {};
|
||||||
|
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
|
||||||
|
let privateKey = wifToDecimal(privateKeyHex, true);
|
||||||
|
if (typeof privateKey.privateKeyDecimal !== "string") throw new Error("Failed to detremine your private key.");
|
||||||
|
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
|
||||||
|
var receiverDerivedKey = deriveSharedKeyReceiver(data.senderPublicKeyString, receiverECKeyData.privateKey);
|
||||||
|
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
|
||||||
|
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
|
||||||
|
return decryptMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sign data using private-key
|
||||||
|
floCrypto.signData = function(data, privateKeyHex) {
|
||||||
|
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||||
|
key.setCompressed(true);
|
||||||
|
var privateKeyArr = key.getBitcoinPrivateKeyByteArray();
|
||||||
|
var privateKey = BigInteger.fromByteArrayUnsigned(privateKeyArr);
|
||||||
|
var messageHash = Crypto.SHA256(data);
|
||||||
|
var messageHashBigInteger = new BigInteger(messageHash);
|
||||||
|
var messageSign = Bitcoin.ECDSA.sign(messageHashBigInteger, key.priv);
|
||||||
|
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||||
|
return sighex;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Verify signatue of the data using public-key
|
||||||
|
floCrypto.verifySign = function(data, signatureHex, publicKeyHex) {
|
||||||
|
var msgHash = Crypto.SHA256(data);
|
||||||
|
var messageHashBigInteger = new BigInteger(msgHash);
|
||||||
|
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||||
|
var signature = Bitcoin.ECDSA.parseSig(sigBytes);
|
||||||
|
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
||||||
|
var verify = Bitcoin.ECDSA.verifyRaw(messageHashBigInteger, signature.r, signature.s, publicKeyPoint);
|
||||||
|
return verify;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generates a new flo ID and returns private-key, public-key and floID
|
||||||
|
const generateNewID = floCrypto.generateNewID = function() {
|
||||||
|
var key = new Bitcoin.ECKey(false);
|
||||||
|
key.setCompressed(true);
|
||||||
|
return {
|
||||||
|
floID: key.getBitcoinAddress(),
|
||||||
|
pubKey: key.getPubKeyHex(),
|
||||||
|
privKey: key.getBitcoinWalletImportFormat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(floCrypto, 'newID', {
|
||||||
|
get: () => generateNewID()
|
||||||
|
});
|
||||||
|
|
||||||
|
//Returns public-key from private-key
|
||||||
|
floCrypto.getPubKeyHex = function(privateKeyHex) {
|
||||||
|
if (!privateKeyHex)
|
||||||
|
return null;
|
||||||
|
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||||
|
if (key.priv == null)
|
||||||
|
return null;
|
||||||
|
key.setCompressed(true);
|
||||||
|
return key.getPubKeyHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Returns flo-ID from public-key or private-key
|
||||||
|
floCrypto.getFloID = function(keyHex) {
|
||||||
|
if (!keyHex)
|
||||||
|
return null;
|
||||||
|
try {
|
||||||
|
var key = new Bitcoin.ECKey(keyHex);
|
||||||
|
if (key.priv == null)
|
||||||
|
key.setPub(keyHex);
|
||||||
|
return key.getBitcoinAddress();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Verify the private-key for the given public-key or flo-ID
|
||||||
|
floCrypto.verifyPrivKey = function(privateKeyHex, pubKey_floID, isfloID = true) {
|
||||||
|
if (!privateKeyHex || !pubKey_floID)
|
||||||
|
return false;
|
||||||
|
try {
|
||||||
|
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||||
|
if (key.priv == null)
|
||||||
|
return false;
|
||||||
|
key.setCompressed(true);
|
||||||
|
if (isfloID && pubKey_floID == key.getBitcoinAddress())
|
||||||
|
return true;
|
||||||
|
else if (!isfloID && pubKey_floID == key.getPubKeyHex())
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the given Address is valid or not
|
||||||
|
floCrypto.validateFloID = floCrypto.validateAddr = function(inpAddr) {
|
||||||
|
if (!inpAddr)
|
||||||
|
return false;
|
||||||
|
try {
|
||||||
|
let addr = new Bitcoin.Address(inpAddr);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Split the str using shamir's Secret and Returns the shares
|
||||||
|
floCrypto.createShamirsSecretShares = function(str, total_shares, threshold_limit) {
|
||||||
|
try {
|
||||||
|
if (str.length > 0) {
|
||||||
|
var strHex = shamirSecretShare.str2hex(str);
|
||||||
|
var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit);
|
||||||
|
return shares;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Returns the retrived secret by combining the shamirs shares
|
||||||
|
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function(sharesArray) {
|
||||||
|
try {
|
||||||
|
if (sharesArray.length > 0) {
|
||||||
|
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
|
||||||
|
comb = shamirSecretShare.hex2str(comb);
|
||||||
|
return comb;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Verifies the shares and str
|
||||||
|
floCrypto.verifyShamirsSecret = function(sharesArray, str) {
|
||||||
|
if (!str)
|
||||||
|
return null;
|
||||||
|
else if (retrieveShamirSecret(sharesArray) === str)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateASCII = floCrypto.validateASCII = function(string, bool = true) {
|
||||||
|
if (typeof string !== "string")
|
||||||
|
return null;
|
||||||
|
if (bool) {
|
||||||
|
let x;
|
||||||
|
for (let i = 0; i < string.length; i++) {
|
||||||
|
x = string.charCodeAt(i);
|
||||||
|
if (x < 32 || x > 127)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
let x, invalids = {};
|
||||||
|
for (let i = 0; i < string.length; i++) {
|
||||||
|
x = string.charCodeAt(i);
|
||||||
|
if (x < 32 || x > 127)
|
||||||
|
if (x in invalids)
|
||||||
|
invalids[string[i]].push(i)
|
||||||
|
else
|
||||||
|
invalids[string[i]] = [i];
|
||||||
|
}
|
||||||
|
if (Object.keys(invalids).length)
|
||||||
|
return invalids;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
floCrypto.convertToASCII = function(string, mode = 'soft-remove') {
|
||||||
|
let chars = validateASCII(string, false);
|
||||||
|
if (chars === true)
|
||||||
|
return string;
|
||||||
|
else if (chars === null)
|
||||||
|
return null;
|
||||||
|
let convertor, result = string,
|
||||||
|
refAlt = {};
|
||||||
|
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
|
||||||
|
mode = mode.toLowerCase();
|
||||||
|
if (mode === "hard-unicode")
|
||||||
|
convertor = (c) => `\\u${('000'+c.charCodeAt().toString(16)).slice(-4)}`;
|
||||||
|
else if (mode === "soft-unicode")
|
||||||
|
convertor = (c) => refAlt[c] || `\\u${('000'+c.charCodeAt().toString(16)).slice(-4)}`;
|
||||||
|
else if (mode === "hard-remove")
|
||||||
|
convertor = c => "";
|
||||||
|
else if (mode === "soft-remove")
|
||||||
|
convertor = c => refAlt[c] || "";
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
for (let c in chars)
|
||||||
|
result = result.replaceAll(c, convertor(c));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
floCrypto.revertUnicode = function(string) {
|
||||||
|
return string.replace(/\\u[\dA-F]{4}/gi,
|
||||||
|
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
|
||||||
|
}
|
||||||
|
|
||||||
|
})('object' === typeof module ? module.exports : window.floCrypto = {});
|
||||||
603
floDapps.js
Normal file
603
floDapps.js
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
(function(EXPORTS) { //floDapps v2.2.0
|
||||||
|
/* General functions for FLO Dapps*/
|
||||||
|
//'use strict';
|
||||||
|
const floDapps = EXPORTS;
|
||||||
|
|
||||||
|
function initIndexedDB() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var obs_g = {
|
||||||
|
//general
|
||||||
|
lastTx: {},
|
||||||
|
//supernode (cloud list)
|
||||||
|
supernodes: {
|
||||||
|
indexes: {
|
||||||
|
uri: null,
|
||||||
|
pubKey: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var obs_a = {
|
||||||
|
//login credentials
|
||||||
|
credentials: {},
|
||||||
|
//for Dapps
|
||||||
|
subAdmins: {},
|
||||||
|
settings: {},
|
||||||
|
appObjects: {},
|
||||||
|
generalData: {},
|
||||||
|
lastVC: {}
|
||||||
|
}
|
||||||
|
//add other given objectStores
|
||||||
|
initIndexedDB.appObs = initIndexedDB.appObs || {}
|
||||||
|
for (o in initIndexedDB.appObs)
|
||||||
|
if (!(o in obs_a))
|
||||||
|
obs_a[o] = initIndexedDB.appObs[o]
|
||||||
|
Promise.all([
|
||||||
|
compactIDB.initDB(floGlobals.application, obs_a),
|
||||||
|
compactIDB.initDB("floDapps", obs_g)
|
||||||
|
]).then(result => {
|
||||||
|
compactIDB.setDefaultDB(floGlobals.application)
|
||||||
|
resolve("IndexedDB App Storage Initated Successfully")
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function initUserDB(floID) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var obs = {
|
||||||
|
contacts: {},
|
||||||
|
pubKeys: {},
|
||||||
|
messages: {}
|
||||||
|
}
|
||||||
|
compactIDB.initDB(`floDapps#${floID}`, obs).then(result => {
|
||||||
|
resolve("UserDB Initated Successfully")
|
||||||
|
}).catch(error => reject('Init userDB failed'));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadUserDB(floID) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var loadData = ["contacts", "pubKeys", "messages"]
|
||||||
|
var promises = []
|
||||||
|
for (var i = 0; i < loadData.length; i++)
|
||||||
|
promises[i] = compactIDB.readAllData(loadData[i], `floDapps#${floID}`)
|
||||||
|
Promise.all(promises).then(results => {
|
||||||
|
for (var i = 0; i < loadData.length; i++)
|
||||||
|
floGlobals[loadData[i]] = results[i]
|
||||||
|
resolve("Loaded Data from userDB")
|
||||||
|
}).catch(error => reject('Load userDB failed'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const startUpFunctions = [];
|
||||||
|
|
||||||
|
startUpFunctions.push(function readSupernodeListFromAPI() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
compactIDB.readData("lastTx", floGlobals.SNStorageID, "floDapps").then(lastTx => {
|
||||||
|
floBlockchainAPI.readData(floGlobals.SNStorageID, {
|
||||||
|
ignoreOld: lastTx,
|
||||||
|
sentOnly: true,
|
||||||
|
pattern: "SuperNodeStorage"
|
||||||
|
}).then(result => {
|
||||||
|
for (var i = result.data.length - 1; i >= 0; i--) {
|
||||||
|
var content = JSON.parse(result.data[i]).SuperNodeStorage;
|
||||||
|
for (sn in content.removeNodes)
|
||||||
|
compactIDB.removeData("supernodes", sn, "floDapps");
|
||||||
|
for (sn in content.newNodes)
|
||||||
|
compactIDB.writeData("supernodes", content.newNodes[sn], sn, "floDapps");
|
||||||
|
}
|
||||||
|
compactIDB.writeData("lastTx", result.totalTxs, floGlobals.SNStorageID, "floDapps");
|
||||||
|
compactIDB.readAllData("supernodes", "floDapps").then(result => {
|
||||||
|
floGlobals.supernodes = result;
|
||||||
|
floCloudAPI.init()
|
||||||
|
.then(result => resolve("Loaded Supernode list\n" + result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
startUpFunctions.push(function readAppConfigFromAPI() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
compactIDB.readData("lastTx", `${floGlobals.application}|${floGlobals.adminID}`, "floDapps").then(lastTx => {
|
||||||
|
floBlockchainAPI.readData(floGlobals.adminID, {
|
||||||
|
ignoreOld: lastTx,
|
||||||
|
sentOnly: true,
|
||||||
|
pattern: floGlobals.application
|
||||||
|
}).then(result => {
|
||||||
|
for (var i = result.data.length - 1; i >= 0; i--) {
|
||||||
|
var content = JSON.parse(result.data[i])[floGlobals.application];
|
||||||
|
if (!content || typeof content !== "object")
|
||||||
|
continue;
|
||||||
|
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", true, content.addSubAdmin[k]);
|
||||||
|
if (content.settings)
|
||||||
|
for (let l in content.settings)
|
||||||
|
compactIDB.writeData("settings", content.settings[l], l)
|
||||||
|
}
|
||||||
|
compactIDB.writeData("lastTx", result.totalTxs, `${floGlobals.application}|${floGlobals.adminID}`, "floDapps");
|
||||||
|
compactIDB.readAllData("subAdmins").then(result => {
|
||||||
|
floGlobals.subAdmins = Object.keys(result);
|
||||||
|
compactIDB.readAllData("settings").then(result => {
|
||||||
|
floGlobals.settings = result;
|
||||||
|
resolve("Read app configuration from blockchain");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
startUpFunctions.push(function loadDataFromAppIDB() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var loadData = ["appObjects", "generalData", "lastVC"]
|
||||||
|
var promises = []
|
||||||
|
for (var 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))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
function getCredentials() {
|
||||||
|
|
||||||
|
const inputFn = getCredentials.privKeyInput ||
|
||||||
|
(type => new Promise((resolve, reject) => {
|
||||||
|
let inputVal = prompt(`Enter ${type}: `)
|
||||||
|
if (inputVal === null)
|
||||||
|
reject(null)
|
||||||
|
else
|
||||||
|
resolve(inputVal)
|
||||||
|
}));
|
||||||
|
|
||||||
|
const readSharesFromIDB = indexArr => new Promise((resolve, reject) => {
|
||||||
|
var promises = []
|
||||||
|
for (var i = 0; i < indexArr.length; i++)
|
||||||
|
promises.push(compactIDB.readData('credentials', indexArr[i]))
|
||||||
|
Promise.all(promises).then(shares => {
|
||||||
|
var secret = floCrypto.retrieveShamirSecret(shares)
|
||||||
|
if (secret)
|
||||||
|
resolve(secret)
|
||||||
|
else
|
||||||
|
reject("Shares are insufficient or incorrect")
|
||||||
|
}).catch(error => {
|
||||||
|
clearCredentials();
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const writeSharesToIDB = (shares, i = 0, resultIndexes = []) => new Promise(resolve => {
|
||||||
|
if (i >= shares.length)
|
||||||
|
return resolve(resultIndexes)
|
||||||
|
var n = floCrypto.randInt(0, 100000)
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPrivateKeyCredentials = () => new Promise((resolve, reject) => {
|
||||||
|
var indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
|
||||||
|
if (indexArr) {
|
||||||
|
readSharesFromIDB(JSON.parse(indexArr))
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
} else {
|
||||||
|
var privKey;
|
||||||
|
inputFn("PRIVATE_KEY").then(result => {
|
||||||
|
if (!result)
|
||||||
|
return reject("Empty Private Key")
|
||||||
|
var floID = floCrypto.getFloID(result)
|
||||||
|
if (!floID || !floCrypto.validateAddr(floID))
|
||||||
|
return reject("Invalid Private Key")
|
||||||
|
privKey = result;
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error, "Generating Random Keys")
|
||||||
|
privKey = floCrypto.generateNewID().privKey
|
||||||
|
}).finally(_ => {
|
||||||
|
if (!privKey)
|
||||||
|
return;
|
||||||
|
var threshold = floCrypto.randInt(10, 20)
|
||||||
|
var shares = floCrypto.createShamirsSecretShares(privKey, threshold, threshold)
|
||||||
|
writeSharesToIDB(shares).then(resultIndexes => {
|
||||||
|
//store index keys in localStorage
|
||||||
|
localStorage.setItem(`${floGlobals.application}#privKey`, JSON.stringify(resultIndexes))
|
||||||
|
//also add a dummy privatekey to the IDB
|
||||||
|
var randomPrivKey = floCrypto.generateNewID().privKey
|
||||||
|
var randomThreshold = floCrypto.randInt(10, 20)
|
||||||
|
var randomShares = floCrypto.createShamirsSecretShares(randomPrivKey, randomThreshold, randomThreshold)
|
||||||
|
writeSharesToIDB(randomShares)
|
||||||
|
//resolve private Key
|
||||||
|
resolve(privKey)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkIfPinRequired = key => new Promise((resolve, reject) => {
|
||||||
|
if (key.length == 52)
|
||||||
|
resolve(key)
|
||||||
|
else {
|
||||||
|
inputFn("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 {
|
||||||
|
myPrivKey = privKey
|
||||||
|
myPubKey = floCrypto.getPubKeyHex(myPrivKey)
|
||||||
|
myFloID = floCrypto.getFloID(myPubKey)
|
||||||
|
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(true, `${result}\nCompleted ${callStartUpFunction.completed}/${callStartUpFunction.total} Startup functions`)
|
||||||
|
resolve(true)
|
||||||
|
}).catch(error => {
|
||||||
|
callStartUpFunction.failed += 1;
|
||||||
|
startUpLog(false, `${error}\nFailed ${callStartUpFunction.failed}/${callStartUpFunction.total} Startup functions`)
|
||||||
|
reject(false)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
var _midFunction;
|
||||||
|
const midStartUp = () => new Promise((res, rej) => {
|
||||||
|
if (_midFunction instanceof Function) {
|
||||||
|
_midFunction()
|
||||||
|
.then(r => res("Mid startup function completed"))
|
||||||
|
.catch(e => rej("Mid startup function failed"))
|
||||||
|
} else
|
||||||
|
res("No mid startup function")
|
||||||
|
});
|
||||||
|
|
||||||
|
const callAndLog = p => new Promise((res, rej) => {
|
||||||
|
p.then(r => {
|
||||||
|
startUpLog(true, r)
|
||||||
|
res(r)
|
||||||
|
}).catch(e => {
|
||||||
|
startUpLog(false, 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(midStartUp())
|
||||||
|
.then(r => res(true))
|
||||||
|
.catch(e => rej(false))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let p2 = new Promise((res, rej) => {
|
||||||
|
callAndLog(getCredentials()).then(r => {
|
||||||
|
callAndLog(initUserDB(myFloID)).then(r => {
|
||||||
|
callAndLog(loadUserDB(myFloID))
|
||||||
|
.then(r => res(true))
|
||||||
|
.catch(e => rej(false))
|
||||||
|
}).catch(e => rej(false))
|
||||||
|
}).catch(e => rej(false))
|
||||||
|
})
|
||||||
|
Promise.all([p1, p2])
|
||||||
|
.then(r => resolve('App Startup finished successful'))
|
||||||
|
.catch(e => reject('App Startup failed'))
|
||||||
|
}).catch(error => reject("App database initiation failed"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
floDapps.addStartUpFunction = fn => fn instanceof Function && !startUpFunctions.includes(fn) ? startUpFunctions.push(fn) : false;
|
||||||
|
|
||||||
|
floDapps.setMidStartup = fn => fn instanceof Function ? _midFunction = fn : false;
|
||||||
|
|
||||||
|
floDapps.setCustomStartupLogger = fn => fn instanceof Function ? startUpLog = fn : false;
|
||||||
|
|
||||||
|
floDapps.setCustomPrivKeyInput = fn => fn instanceof Function ? customFn = fn : false;
|
||||||
|
|
||||||
|
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, `floDapps#${myFloID}`).then(result => {
|
||||||
|
floGlobals.contacts[floID] = name;
|
||||||
|
resolve("Contact stored")
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
floDapps.storePubKey = function(floID, pubKey) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (floID in floGlobals.pubKeys)
|
||||||
|
return resolve("pubKey already stored")
|
||||||
|
if (!floCrypto.validateAddr(floID))
|
||||||
|
return reject("Invalid floID!")
|
||||||
|
if (floCrypto.getFloID(pubKey) != floID)
|
||||||
|
return reject("Incorrect pubKey")
|
||||||
|
compactIDB.writeData("pubKeys", pubKey, floID, `floDapps#${myFloID}`).then(result => {
|
||||||
|
floGlobals.pubKeys[floID] = pubKey;
|
||||||
|
resolve("pubKey stored")
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
floDapps.sendMessage = function(floID, message) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let options = {
|
||||||
|
receiverID: floID,
|
||||||
|
application: "floDapps",
|
||||||
|
comment: floGlobals.application
|
||||||
|
}
|
||||||
|
if (floID in floGlobals.pubKeys)
|
||||||
|
message = floCrypto.encryptData(JSON.stringify(message), floGlobals.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(floGlobals.messages).sort().pop()
|
||||||
|
let options = {
|
||||||
|
receiverID: myFloID,
|
||||||
|
application: "floDapps",
|
||||||
|
lowerVectorClock: lastVC + 1
|
||||||
|
}
|
||||||
|
options.callback = (d, e) => {
|
||||||
|
for (let v in d) {
|
||||||
|
try {
|
||||||
|
if (d[v].message instanceof Object && "secret" in d[v].message)
|
||||||
|
d[v].message = floCrypto.decryptData(d[v].message, myPrivKey)
|
||||||
|
} catch (error) {}
|
||||||
|
compactIDB.writeData("messages", d[v], v, `floDapps#${myFloID}`)
|
||||||
|
floGlobals.messages[v] = d[v]
|
||||||
|
}
|
||||||
|
if (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 (!Array.isArray(addList) || !addList.length) addList = undefined;
|
||||||
|
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
|
||||||
|
if (!settings || typeof settings !== "object" || !Object.keys(settings).length) settings = undefined;
|
||||||
|
if (!addList && !rmList && !settings)
|
||||||
|
return reject("No configuration change")
|
||||||
|
var floData = {
|
||||||
|
[floGlobals.application]: {
|
||||||
|
addSubAdmin: addList,
|
||||||
|
removeSubAdmin: rmList,
|
||||||
|
settings: settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var floID = floCrypto.getFloID(adminPrivKey)
|
||||||
|
if (floID != floGlobals.adminID)
|
||||||
|
reject('Access Denied for Admin privilege')
|
||||||
|
else
|
||||||
|
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', floGlobals.application).then(result => {
|
||||||
|
localStorage.removeItem(`${floGlobals.application}#privKey`)
|
||||||
|
myPrivKey = myPubKey = myFloID = undefined;
|
||||||
|
resolve("privKey credentials deleted!")
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
floDapps.deleteUserData = function(credentials = false) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let p = []
|
||||||
|
p.push(compactIDB.deleteDB(`floDapps#${myFloID}`))
|
||||||
|
if (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(floGlobals.application).then(result => {
|
||||||
|
localStorage.removeItem(`${floGlobals.application}#privKey`)
|
||||||
|
myPrivKey = myPubKey = myFloID = undefined;
|
||||||
|
compactIDB.removeData('lastTx', `${floGlobals.application}|${floGlobals.adminID}`, 'floDapps')
|
||||||
|
.then(result => resolve("App database(local) deleted"))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
floDapps.securePrivKey = function(pwd) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
|
||||||
|
if (!indexArr)
|
||||||
|
return reject("PrivKey not found");
|
||||||
|
indexArr = JSON.parse(indexArr)
|
||||||
|
let encryptedKey = Crypto.AES.encrypt(myPrivKey, pwd);
|
||||||
|
let threshold = indexArr.length;
|
||||||
|
let shares = floCrypto.createShamirsSecretShares(encryptedKey, threshold, threshold)
|
||||||
|
let promises = [];
|
||||||
|
let overwriteFn = (share, index) =>
|
||||||
|
compactIDB.writeData("credentials", share, index, floGlobals.application);
|
||||||
|
for (var i = 0; i < threshold; i++)
|
||||||
|
promises.push(overwriteFn(shares[i], indexArr[i]));
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(results => resolve("Private Key Secured"))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
floDapps.verifyPin = function(pin = null) {
|
||||||
|
const readSharesFromIDB = function(indexArr) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var promises = []
|
||||||
|
for (var 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)
|
||||||
|
if (secret)
|
||||||
|
resolve(secret)
|
||||||
|
else
|
||||||
|
reject("Shares are insufficient or incorrect")
|
||||||
|
}).catch(error => {
|
||||||
|
clearCredentials();
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
|
||||||
|
console.info(indexArr)
|
||||||
|
if (!indexArr)
|
||||||
|
reject('No login credentials found')
|
||||||
|
readSharesFromIDB(JSON.parse(indexArr)).then(key => {
|
||||||
|
if (key.length == 52) {
|
||||||
|
if (pin === null)
|
||||||
|
resolve("Private key not secured")
|
||||||
|
else
|
||||||
|
reject("Private key not secured")
|
||||||
|
} else {
|
||||||
|
if (pin === null)
|
||||||
|
return reject("PIN/Password required")
|
||||||
|
try {
|
||||||
|
let privKey = 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])
|
||||||
|
if (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])
|
||||||
|
if (d > vectorClock && floGlobals.generalData[fk][d].comment == comment)
|
||||||
|
filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d]))
|
||||||
|
}
|
||||||
|
if (options.decrypt) {
|
||||||
|
let decryptionKey = (options.decrypt === true) ? myPrivKey : options.decrypt;
|
||||||
|
if (!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) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getNextGeneralData[fk] = Object.keys(filteredResult).sort().pop();
|
||||||
|
return filteredResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncData = floDapps.syncData = {};
|
||||||
|
|
||||||
|
syncData.oldDevice = () => new Promise((resolve, reject) => {
|
||||||
|
let sync = {
|
||||||
|
contacts: floGlobals.contacts,
|
||||||
|
pubKeys: floGlobals.pubKeys,
|
||||||
|
messages: floGlobals.messages
|
||||||
|
}
|
||||||
|
let message = Crypto.AES.encrypt(JSON.stringify(sync), myPrivKey)
|
||||||
|
let options = {
|
||||||
|
receiverID: myFloID,
|
||||||
|
application: "floDapps"
|
||||||
|
}
|
||||||
|
floCloudAPI.sendApplicationData(message, "syncData", options)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
});
|
||||||
|
|
||||||
|
syncData.newDevice = () => new Promise((resolve, reject) => {
|
||||||
|
var options = {
|
||||||
|
receiverID: myFloID,
|
||||||
|
senderID: myFloID,
|
||||||
|
application: "floDapps",
|
||||||
|
mostRecent: true,
|
||||||
|
}
|
||||||
|
floCloudAPI.requestApplicationData("syncData", options).then(response => {
|
||||||
|
let vc = Object.keys(response).sort().pop()
|
||||||
|
let sync = JSON.parse(Crypto.AES.decrypt(response[vc].message, myPrivKey))
|
||||||
|
let promises = []
|
||||||
|
let store = (key, val, obs) => promises.push(compactIDB.writeData(obs, val, key, `floDapps#${floID}`));
|
||||||
|
["contacts", "pubKeys", "messages"].forEach(c => {
|
||||||
|
for (let i in sync[c]) {
|
||||||
|
store(i, sync[c][i], c)
|
||||||
|
floGlobals[c][i] = sync[c][i]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(results => resolve("Sync data successful"))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
})('object' === typeof module ? module.exports : window.floDapps = {})
|
||||||
61
index.html
Normal file
61
index.html
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>FLO Standard Operators</title>
|
||||||
|
<script id="floGlobals">
|
||||||
|
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
||||||
|
const floGlobals = {
|
||||||
|
|
||||||
|
//Required for all
|
||||||
|
blockchain: "FLO",
|
||||||
|
|
||||||
|
//Required for blockchain API operators
|
||||||
|
apiURL: {
|
||||||
|
FLO: ['https://livenet.flocha.in/', 'https://flosight.duckdns.org/'],
|
||||||
|
FLO_TEST: ['https://testnet-flosight.duckdns.org/', 'https://testnet.flocha.in/']
|
||||||
|
},
|
||||||
|
adminID: "FKAEdnPfjXLHSYwrXQu377ugN4tXU7VGdf",
|
||||||
|
sendAmt: 0.001,
|
||||||
|
fee: 0.0005,
|
||||||
|
|
||||||
|
//Required for Supernode operations
|
||||||
|
SNStorageID: "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk",
|
||||||
|
supernodes: {}, //each supnernode must be stored as floID : {uri:<uri>,pubKey:<publicKey>}
|
||||||
|
|
||||||
|
//for cloud apps
|
||||||
|
subAdmins: [],
|
||||||
|
application: "TEST_MODE",
|
||||||
|
appObjects: {},
|
||||||
|
generalData: {},
|
||||||
|
lastVC: {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="lib.js"></script>
|
||||||
|
<script src="floCrypto.js"></script>
|
||||||
|
<script src="floBlockchainAPI.js"></script>
|
||||||
|
<script src="compactIDB.js"></script>
|
||||||
|
<script src="floCloudAPI.js"></script>
|
||||||
|
<script src="floDapps.js"></script>
|
||||||
|
<script id="onLoadStartUp">
|
||||||
|
function onLoadStartUp() {
|
||||||
|
|
||||||
|
//floDapps.addStartUpFunction('Sample', Promised Function)
|
||||||
|
//floDapps.setAppObjectStores({sampleObs1:{}, sampleObs2:{options{autoIncrement:true, keyPath:'SampleKey'}, Indexes:{sampleIndex:{}}}})
|
||||||
|
//floDapps.setCustomPrivKeyInput( () => { FUNCTION BODY *must resolve private key* } )
|
||||||
|
|
||||||
|
floDapps.launchStartUp().then(result => {
|
||||||
|
console.log(result)
|
||||||
|
alert(`Welcome FLO_ID: ${myFloID}`)
|
||||||
|
//App functions....
|
||||||
|
}).catch(error => console.error(error))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="onLoadStartUp()">
|
||||||
|
TEST_MODE
|
||||||
|
(use console)
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user