Compare commits

...

13 Commits

Author SHA1 Message Date
b70b2211ab
Update floDapps.js
Some checks failed
Workflow push to Dappbundle / Build (push) Has been cancelled
2025-08-28 13:11:29 +05:30
f51619f8dd
Update floCloudAPI.js 2025-08-28 13:05:31 +05:30
0494bc6d3f
Update push-dappbundle.yml 2025-08-25 08:00:56 +05:30
9ea694d160
Update floCloudAPI.js
Updating offline error capabilities
2025-08-25 07:58:28 +05:30
4d6f7c68f2
Update floDapps.js
Improving offline capabilities
2025-08-25 07:57:16 +05:30
sairaj mote
b8d0d50039 Moving to new tor links 2024-01-21 01:26:36 +05:30
sairaj mote
27739db63b updating floBlockchainAPI module 2024-01-19 01:28:41 +05:30
sairaj mote
d200b1f6c2 preserving white space 2024-01-18 02:06:10 +05:30
sairaj mote
7e75aadc40 Create push-dappbundle.yml 2023-12-18 22:05:45 +05:30
sairaj mote
dc62b016fb Merge https://github.com/ranchimall/standard-operations 2023-07-30 15:35:52 +05:30
sairajzero
098a62047c floCloudAPI v2.4.5 : file support
- uploadFile: upload a file to the cloud
- downloadFile: download a file from cloud

- upload/download file supports encryption of file. use options.encrypt in uploadFile and options.decrypt in downloadFile
2023-07-29 05:41:06 +05:30
sairajzero
5be35c28ce floDapps v2.4.1
- support for multiple cloud networks
2023-07-29 05:01:14 +05:30
sairajzero
471f291848 floCloudAPI v2.4.4: edit comment feature
- senders can now edit the comment of data they sent (by re-signing the new data)
2023-07-26 03:45:35 +05:30
10 changed files with 644 additions and 201 deletions

32
.github/workflows/push-dappbundle.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Workflow push to Dappbundle
on: [push]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Executing remote command
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.R_HOST }}
username: ${{ secrets.P_USERNAME }}
password: ${{ secrets.P_PASSWORD }}
port: ${{ secrets.SSH_PORT }}
script: |
if [ -d "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle" ]; then
echo "Folder exists. Skipping Git clone."
else
echo "Folder does not exist. Cloning repository..."
cd ${{ secrets.DEPLOYMENT_LOCATION}}/ && git clone https://github.com/ranchimall/dappbundle.git
fi
if [ -d "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}" ]; then
echo "Repository exists. Remove folder "
rm -r "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}"
fi
echo "Cloning repository..."
cd ${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle && git clone https://github.com/ranchimall/${{ github.event.repository.name }}
cd "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}" && rm -rf .gitattributes .git .github .gitignore
cd ${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/ && git add . && git commit -m "Workflow updating files of ${{ github.event.repository.name }}" && git push "https://ranchimalldev:${{ secrets.RM_ACCESS_TOKEN }}@github.com/ranchimall/dappbundle.git"

View File

@ -1069,6 +1069,7 @@ sm-copy {
}
#preview_page p {
font-size: 1rem;
white-space: pre-line;
}
#preview_page p * {
font-size: inherit;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

1
scripts/floBlockchainAPI.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
(function (EXPORTS) { //floCloudAPI v2.4.3a
(function (EXPORTS) { //floCloudAPI v2.4.5a
/* FLO Cloud operations to send/request application data*/
'use strict';
const floCloudAPI = EXPORTS;
@ -195,14 +195,24 @@
floCloudAPI.init = function startCloudProcess(nodes) {
return new Promise((resolve, reject) => {
try {
// accept only plain-ish objects
nodes = (nodes && typeof nodes === 'object' && !Array.isArray(nodes)) ? nodes : {};
supernodes = nodes;
// reset liveness bookkeeping for the new set
if (_inactive && typeof _inactive.clear === 'function') _inactive.clear();
// (re)build the bucket with current IDs
kBucket = new K_Bucket(DEFAULT.SNStorageID, Object.keys(supernodes));
resolve('Cloud init successful');
} catch (error) {
reject(error);
}
})
}
});
};
Object.defineProperty(floCloudAPI, 'kBucket', {
get: () => kBucket
@ -227,24 +237,27 @@
function ws_activeConnect(snID, reverse = false) {
return new Promise((resolve, reject) => {
if (_inactive.size === kBucket.list.length)
// Safe guard: uninitialized kBucket, empty list, or all inactive
if (!kBucket || !kBucket.list || !kBucket.list.length || _inactive.size === kBucket.list.length)
return reject('Cloud offline');
if (!(snID in supernodes))
snID = kBucket.closestNode(proxyID(snID));
if (!(snID in supernodes)) {
var closest = kBucket.closestNode(proxyID(snID));
if (!closest) return reject('Cloud offline'); // no candidate to try
snID = closest;
}
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))
})
})
var nxtNode = reverse ? kBucket.prevNode(snID) : kBucket.nextNode(snID);
if (!nxtNode || nxtNode === snID) return reject('Cloud offline'); // nothing else to try
ws_activeConnect(nxtNode, reverse).then(resolve).catch(reject);
});
});
}
function fetch_API(snID, data) {
return new Promise((resolve, reject) => {
if (_inactive.has(snID))
@ -265,25 +278,28 @@
function fetch_ActiveAPI(snID, data, reverse = false) {
return new Promise((resolve, reject) => {
if (_inactive.size === kBucket.list.length)
// Safe guard: uninitialized kBucket, empty list, or all inactive
if (!kBucket || !kBucket.list || !kBucket.list.length || _inactive.size === kBucket.list.length)
return reject('Cloud offline');
if (!(snID in supernodes))
snID = kBucket.closestNode(proxyID(snID));
if (!(snID in supernodes)) {
var closest = kBucket.closestNode(proxyID(snID));
if (!closest) return reject('Cloud offline'); // no candidate available
snID = closest;
}
fetch_API(snID, data)
.then(result => resolve(result))
.then(resolve)
.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));
})
})
_inactive.add(snID);
var nxtNode = reverse ? kBucket.prevNode(snID) : kBucket.nextNode(snID);
if (!nxtNode || nxtNode === snID) return reject('Cloud offline'); // nothing else to try
fetch_ActiveAPI(nxtNode, data, reverse).then(resolve).catch(reject);
});
});
}
function singleRequest(floID, data_obj, method = "POST") {
return new Promise((resolve, reject) => {
let data;
@ -374,13 +390,31 @@
const util = floCloudAPI.util = {};
//Updating encoding/Decoding to modern standards
const encodeMessage = util.encodeMessage = function (message) {
return btoa(unescape(encodeURIComponent(JSON.stringify(message))))
}
const bytes = new TextEncoder().encode(JSON.stringify(message));
let bin = "";
for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
return btoa(bin);
};
const decodeMessage = util.decodeMessage = function (message) {
return JSON.parse(decodeURIComponent(escape(atob(message))))
}
try {
// try modern decode first
const bin = atob(message);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
return JSON.parse(new TextDecoder().decode(bytes));
} catch (e1) {
try {
// fallback to legacy decode
return JSON.parse(decodeURIComponent(escape(atob(message))));
} catch (e2) {
// final fallback: return raw string to avoid hard crash
return message;
}
}
};
const filterKey = util.filterKey = function (type, options = {}) {
return type + (options.comment ? ':' + options.comment : '') +
@ -468,21 +502,59 @@
function storeGeneral(fk, dataSet) {
try {
console.log(dataSet)
if (typeof generalData[fk] !== "object")
generalData[fk] = {}
for (let vc in dataSet) {
generalData[fk][vc] = dataSet[vc];
if (dataSet[vc].log_time > lastVC[fk])
lastVC[fk] = dataSet[vc].log_time;
if (!dataSet || typeof dataSet !== "object") return;
// Ensure containers exist
if (typeof generalData[fk] !== "object" || generalData[fk] === null)
generalData[fk] = {};
if (typeof lastVC[fk] !== "number")
lastVC[fk] = 0;
// Merge data and track latest log_time
let updated = false;
let newLast = lastVC[fk];
for (const vc in dataSet) {
const rec = dataSet[vc];
// Skip bad records
if (!rec || typeof rec !== "object") continue;
// Assign only if changed reference (cheap check)
if (generalData[fk][vc] !== rec) {
generalData[fk][vc] = rec;
updated = true;
}
if (typeof rec.log_time === "number" && rec.log_time > newLast) {
newLast = rec.log_time;
updated = true;
}
}
compactIDB.writeData("lastVC", lastVC[fk], fk)
compactIDB.writeData("generalData", generalData[fk], fk)
if (!updated) return; // nothing new, avoid IDB writes
lastVC[fk] = newLast;
// --- Debounce writes per fk to avoid IDB thrash ---
storeGeneral._pending = storeGeneral._pending || Object.create(null);
const pend = storeGeneral._pending;
clearTimeout(pend[fk]);
pend[fk] = setTimeout(() => {
try {
// Fire-and-forget; callers dont wait on these
compactIDB.writeData("lastVC", lastVC[fk], fk);
compactIDB.writeData("generalData", generalData[fk], fk);
} catch (e) {
console.error(e);
}
}, 50);
} catch (error) {
console.error(error)
console.error(error);
}
}
function objectifier(data) {
if (!Array.isArray(data))
data = [data];
@ -551,7 +623,7 @@
}
//request any data from supernode cloud
const requestApplicationData = floCloudAPI.requestApplicationData = function (type, options = {}) {
const _requestApplicationData = function (type, options = {}) {
return new Promise((resolve, reject) => {
var request = {
receiverID: options.receiverID || DEFAULT.adminID,
@ -582,6 +654,17 @@
})
}
floCloudAPI.requestApplicationData = function (type, options = {}) {
return new Promise((resolve, reject) => {
let single_request_mode = !(options.callback instanceof Function);
_requestApplicationData(type, options).then(data => {
if (single_request_mode)
resolve(objectifier(data))
else resolve(data);
}).catch(error => reject(error))
})
}
/*(NEEDS UPDATE)
//delete data from supernode cloud (received only)
floCloudAPI.deleteApplicationData = function(vectorClocks, options = {}) {
@ -609,49 +692,39 @@
})
}
*/
/*(NEEDS UPDATE)
//edit comment of data in supernode cloud (mutable comments only)
floCloudAPI.editApplicationData = function(vectorClock, newComment, oldData, options = {}) {
//edit comment of data in supernode cloud (sender only)
floCloudAPI.editApplicationData = function (vectorClock, comment_edit, options = {}) {
return new Promise((resolve, reject) => {
let p0
if (!oldData) {
options.atVectorClock = vectorClock;
options.callback = false;
p0 = requestApplicationData(false, options)
} else
p0 = Promise.resolve({
vectorClock: {
...oldData
}
})
p0.then(d => {
if (d.senderID != user.id)
return reject("Invalid requestorID")
else if (!d.comment.startsWith("EDIT:"))
return reject("Data immutable")
let data = {
//request the data from cloud for resigning
let req_options = Object.assign({}, options);
req_options.atVectorClock = vectorClock;
_requestApplicationData(undefined, req_options).then(result => {
if (!result.length)
return reject("Data not found");
let data = result[0];
if (data.senderID !== user.id)
return reject("Only sender can edit comment");
data.comment = comment_edit;
let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"]
.map(d => data[d]).join("|");
let re_sign = user.sign(hashcontent);
var request = {
receiverID: options.receiverID || DEFAULT.adminID,
requestorID: user.id,
receiverID: d.receiverID,
pubKey: user.public,
time: Date.now(),
application: d.application,
edit: {
vectorClock: vectorClock,
comment: newComment
}
vectorClock: vectorClock,
edit: comment_edit,
re_sign: re_sign
}
d.comment = data.edit.comment;
let hashcontent = ["receiverID", "time", "application", "type", "message",
"comment"
]
.map(x => d[x]).join("|")
data.edit.sign = user.sign(hashcontent)
singleRequest(data.receiverID, data)
.then(result => resolve("Data comment updated"))
let request_hash = ["time", "vectorClock", "edit", "re_sign"].map(d => request[d]).join("|");
request.sign = user.sign(request_hash);
singleRequest(request.receiverID, request)
.then(result => resolve(result))
.catch(error => reject(error))
})
}).catch(error => reject(error))
})
}
*/
//tag data in supernode cloud (subAdmin access only)
floCloudAPI.tagApplicationData = function (vectorClock, tag, options = {}) {
@ -719,11 +792,11 @@
storeGeneral(fk, d);
options.callback(d, e)
}
requestApplicationData(type, new_options)
_requestApplicationData(type, new_options)
.then(result => resolve(result))
.catch(error => reject(error))
} else {
requestApplicationData(type, options).then(dataSet => {
_requestApplicationData(type, options).then(dataSet => {
storeGeneral(fk, objectifier(dataSet))
resolve(dataSet)
}).catch(error => reject(error))
@ -748,7 +821,7 @@
}
delete options.callback;
}
requestApplicationData(objectName, options).then(dataSet => {
_requestApplicationData(objectName, options).then(dataSet => {
updateObject(objectName, objectifier(dataSet));
delete options.comment;
options.lowerVectorClock = lastVC[objectName] + 1;
@ -756,11 +829,11 @@
if (callback) {
let new_options = Object.create(options);
new_options.callback = callback;
requestApplicationData(objectName, new_options)
_requestApplicationData(objectName, new_options)
.then(result => resolve(result))
.catch(error => reject(error))
} else {
requestApplicationData(objectName, options).then(dataSet => {
_requestApplicationData(objectName, options).then(dataSet => {
updateObject(objectName, objectifier(dataSet))
resolve(appObjects[objectName])
}).catch(error => reject(error))
@ -811,6 +884,67 @@
})
}
//upload file
floCloudAPI.uploadFile = function (fileBlob, type, options = {}) {
return new Promise((resolve, reject) => {
if (!(fileBlob instanceof File) && !(fileBlob instanceof Blob))
return reject("file must be instance of File/Blob");
fileBlob.arrayBuffer().then(arraybuf => {
let file_data = { type: fileBlob.type, name: fileBlob.name };
file_data.content = Crypto.util.bytesToBase64(new Uint8Array(arraybuf));
if (options.encrypt) {
let encryptionKey = options.encrypt === true ?
floGlobals.settings.encryptionKey : options.encrypt
file_data = floCrypto.encryptData(JSON.stringify(file_data), encryptionKey)
}
sendApplicationData(file_data, type, options)
.then(({ vectorClock, receiverID, type, application }) => resolve({ vectorClock, receiverID, type, application }))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
//download file
floCloudAPI.downloadFile = function (vectorClock, options = {}) {
return new Promise((resolve, reject) => {
options.atVectorClock = vectorClock;
_requestApplicationData(options.type, options).then(result => {
if (!result.length)
return reject("File not found");
result = result[0];
try {
let file_data = decodeMessage(result.message);
//file is encrypted: decryption required
if (file_data instanceof Object && "secret" in file_data) {
if (!options.decrypt)
return reject("Data is encrypted");
let decryptionKey = (options.decrypt === true) ? Crypto.AES.decrypt(user_private, aes_key) : options.decrypt;
if (!Array.isArray(decryptionKey))
decryptionKey = [decryptionKey];
let flag = false;
for (let key of decryptionKey) {
try {
let tmp = floCrypto.decryptData(file_data, key);
file_data = JSON.parse(tmp);
flag = true;
break;
} catch (error) { }
}
if (!flag)
return reject("Unable to decrypt file: Invalid private key");
}
//reconstruct the file
let arraybuf = new Uint8Array(Crypto.util.base64ToBytes(file_data.content))
result.file = new File([arraybuf], file_data.name, { type: file_data.type });
resolve(result)
} catch (error) {
console.error(error);
reject("Data is not a file");
}
}).catch(error => reject(error))
})
}
/*
Functions:
findDiff(original, updatedObj) returns an object with the added, deleted and updated differences
@ -1052,4 +1186,4 @@
})();
})('object' === typeof module ? module.exports : window.floCloudAPI = {});
})('object' === typeof module ? module.exports : window.floCloudAPI = {});

View File

@ -1,4 +1,4 @@
(function (EXPORTS) { //floDapps v2.4.0
(function (EXPORTS) { //floDapps v2.4.1
/* General functions for FLO Dapps*/
'use strict';
const floDapps = EXPORTS;
@ -144,7 +144,7 @@
}
});
var subAdmins, trustedIDs, settings;
var subAdmins = [], trustedIDs = [], settings = {};
Object.defineProperties(floGlobals, {
subAdmins: {
get: () => subAdmins
@ -172,12 +172,7 @@
//general
lastTx: {},
//supernode (cloud list)
supernodes: {
indexes: {
uri: null,
pubKey: null
}
}
supernodes: {}
}
var obs_a = {
//login credentials
@ -254,103 +249,154 @@
const startUpFunctions = [];
startUpFunctions.push(function readSupernodeListFromAPI() {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
if (!startUpOptions.cloud)
return resolve("No cloud for this app");
compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => {
var query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName };
if (typeof lastTx == 'number') //lastTx is tx count (*backward support)
query_options.ignoreOld = lastTx;
else if (typeof lastTx == 'string') //lastTx is txid of last tx
query_options.after = lastTx;
//fetch data from flosight
floBlockchainAPI.readData(floCloudAPI.SNStorageID, query_options).then(result => {
for (var i = result.data.length - 1; i >= 0; i--) {
var content = JSON.parse(result.data[i])[floCloudAPI.SNStorageName];
for (let sn in content.removeNodes)
compactIDB.removeData("supernodes", sn, DEFAULT.root);
for (let sn in content.newNodes)
compactIDB.writeData("supernodes", content.newNodes[sn], sn, DEFAULT.root);
for (let sn in content.updateNodes)
compactIDB.readData("supernodes", sn, DEFAULT.root).then(r => {
r = r || {}
r.uri = content.updateNodes[sn];
compactIDB.writeData("supernodes", r, sn, DEFAULT.root);
});
}
compactIDB.writeData("lastTx", result.lastItem, floCloudAPI.SNStorageID, DEFAULT.root);
compactIDB.readAllData("supernodes", DEFAULT.root).then(nodes => {
floCloudAPI.init(nodes)
.then(result => resolve("Loaded Supernode list\n" + result))
.catch(error => reject(error))
const CLOUD_KEY = "floCloudAPI#" + floCloudAPI.SNStorageID;
// Fallback: init from cached nodes (never reject)
const initFromCache = (tag) =>
compactIDB.readData("supernodes", CLOUD_KEY, DEFAULT.root)
.then(nodes => {
nodes = nodes || {};
return floCloudAPI.init(nodes)
.then(r => resolve(`${tag} (from cache)\n${r}`))
.catch(() => resolve(`${tag} (cache present, init skipped)`));
})
})
}).catch(error => reject(error))
})
.catch(() => resolve(`${tag} (no cache)`));
compactIDB.readData("lastTx", CLOUD_KEY, DEFAULT.root).then(lastTx => {
const query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName };
if (typeof lastTx === 'number') // backward support (tx count)
query_options.ignoreOld = lastTx;
else if (typeof lastTx === 'string') // last txid
query_options.after = lastTx;
// Try online; if it fails, fall back to cache
floBlockchainAPI.readData(floCloudAPI.SNStorageID, query_options).then(result => {
compactIDB.readData("supernodes", CLOUD_KEY, DEFAULT.root).then(nodes => {
nodes = nodes || {};
for (let i = result.data.length - 1; i >= 0; i--) {
const content = JSON.parse(result.data[i])[floCloudAPI.SNStorageName];
if (!content || typeof content !== 'object') continue;
if (content.removeNodes)
for (let sn in content.removeNodes) delete nodes[sn];
if (content.newNodes)
for (let sn in content.newNodes) nodes[sn] = content.newNodes[sn];
if (content.updateNodes)
for (let sn in content.updateNodes)
if (sn in nodes) nodes[sn].uri = content.updateNodes[sn];
}
Promise.all([
compactIDB.writeData("lastTx", result.lastItem, CLOUD_KEY, DEFAULT.root),
compactIDB.writeData("supernodes", nodes, CLOUD_KEY, DEFAULT.root)
]).then(() => {
floCloudAPI.init(nodes)
.then(r => resolve("Loaded Supernode list\n" + r))
.catch(() => resolve("Loaded Supernode list (init deferred)"));
}).catch(() => resolve("Supernode list updated (persist partial)"));
}).catch(() => initFromCache("Supernode list read failed"));
}).catch(() => initFromCache("Supernode network fetch failed"));
}).catch(() => initFromCache("Supernode lastTx read failed"));
});
});
startUpFunctions.push(function readAppConfigFromAPI() {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
if (!startUpOptions.app_config)
return resolve("No configs for this app");
compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => {
var query_options = { sentOnly: true, pattern: DEFAULT.application };
if (typeof lastTx == 'number') //lastTx is tx count (*backward support)
query_options.ignoreOld = lastTx;
else if (typeof lastTx == 'string') //lastTx is txid of last tx
query_options.after = lastTx;
//fetch data from flosight
// small helper: load cached directives into memory and resolve
const loadFromIDB = (msg) => Promise.all([
compactIDB.readAllData("subAdmins"),
compactIDB.readAllData("trustedIDs"),
compactIDB.readAllData("settings")
]).then(([sub, trust, set]) => {
subAdmins = Object.keys(sub || {}); // arrays of IDs
trustedIDs = Object.keys(trust || {});
settings = set || {};
resolve(msg);
}).catch(() => {
// safe defaults if cache missing
subAdmins = []; trustedIDs = []; settings = {};
resolve(msg + " (no local cache)");
});
// If cloud is disabled, use cached config and move on
if (!startUpOptions.cloud)
return loadFromIDB("Read app configuration from local cache (offline)");
const lastKey = `${DEFAULT.application}|${DEFAULT.adminID}`;
// Try to read lastTx; on failure, just use cache (dont block startup)
compactIDB.readData("lastTx", lastKey, DEFAULT.root).then(lastTx => {
const query_options = { sentOnly: true, pattern: DEFAULT.application };
if (typeof lastTx === 'number') query_options.ignoreOld = lastTx;
else if (typeof lastTx === 'string') query_options.after = lastTx;
// Fetch deltas from chain; on failure, fall back to cache
floBlockchainAPI.readData(DEFAULT.adminID, query_options).then(result => {
for (var i = result.data.length - 1; i >= 0; i--) {
var content = JSON.parse(result.data[i])[DEFAULT.application];
if (!content || typeof content !== "object")
continue;
for (let i = result.data.length - 1; i >= 0; i--) {
const content = JSON.parse(result.data[i])[DEFAULT.application];
if (!content || typeof content !== "object") continue;
if (Array.isArray(content.removeSubAdmin))
for (var j = 0; j < content.removeSubAdmin.length; j++)
for (let 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++)
for (let k = 0; k < content.addSubAdmin.length; k++)
compactIDB.writeData("subAdmins", true, content.addSubAdmin[k]);
if (Array.isArray(content.removeTrustedID))
for (var j = 0; j < content.removeTrustedID.length; j++)
for (let j = 0; j < content.removeTrustedID.length; j++)
compactIDB.removeData("trustedIDs", content.removeTrustedID[j]);
if (Array.isArray(content.addTrustedID))
for (var k = 0; k < content.addTrustedID.length; k++)
for (let k = 0; k < content.addTrustedID.length; k++)
compactIDB.writeData("trustedIDs", true, content.addTrustedID[k]);
if (content.settings)
for (let l in content.settings)
compactIDB.writeData("settings", content.settings[l], l)
compactIDB.writeData("settings", content.settings[l], l);
}
compactIDB.writeData("lastTx", result.lastItem, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root);
compactIDB.readAllData("subAdmins").then(result => {
subAdmins = Object.keys(result);
compactIDB.readAllData("trustedIDs").then(result => {
trustedIDs = Object.keys(result);
compactIDB.readAllData("settings").then(result => {
settings = result;
resolve("Read app configuration from blockchain");
})
})
})
})
}).catch(error => reject(error))
})
// persist last item marker (best effort)
compactIDB.writeData("lastTx", result.lastItem, lastKey, DEFAULT.root).catch(() => {});
// load fresh values from IDB into memory and finish
loadFromIDB("Read app configuration from blockchain");
}).catch(() => {
// network failed → boot from cache
loadFromIDB("Read app configuration from local cache (network fail)");
});
}).catch(() => {
// couldn't read lastTx → still boot from cache
loadFromIDB("Read app configuration from local cache (no lastTx)");
});
});
});
startUpFunctions.push(function loadDataFromAppIDB() {
return new Promise((resolve, reject) => {
if (!startUpOptions.cloud)
return resolve("No cloud for this app");
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))
})
const loadData = ["appObjects", "generalData", "lastVC"];
// If cloud is disabled AND no IDB stores are expected, skip early
if (!startUpOptions.cloud && (!initIndexedDB.appObs || Object.keys(initIndexedDB.appObs).length === 0))
return resolve("No cloud and no local data to load");
// Otherwise, read from IDB
Promise.all(loadData.map(item => compactIDB.readAllData(item)))
.then(results => {
for (let i = 0; i < loadData.length; i++)
floGlobals[loadData[i]] = results[i];
resolve("Loaded Data from app IDB");
})
.catch(error => reject(error));
});
});
var keyInput = type => new Promise((resolve, reject) => {
@ -842,4 +888,4 @@
.catch(error => reject(error))
}).catch(error => reject(error))
});
})('object' === typeof module ? module.exports : window.floDapps = {});
})('object' === typeof module ? module.exports : window.floDapps = {});

File diff suppressed because one or more lines are too long

1
scripts/floTokenAPI.min.js vendored Normal file

File diff suppressed because one or more lines are too long