Merge pull request #2 from ranchimall/master
This commit is contained in:
commit
62ac2a9371
112
README.md
112
README.md
@ -78,3 +78,115 @@ This template contains standard operations that can be used for the following:
|
|||||||
1. data - data of the given signature
|
1. data - data of the given signature
|
||||||
2. signature - signature of the data
|
2. signature - signature of the data
|
||||||
3. publicKey - public key of the signer
|
3. publicKey - public key of the signer
|
||||||
|
|
||||||
|
## FLO Blockchain API Operations
|
||||||
|
`floBlockchainAPI` object method can be used to send/recieve data to/from blockchain.These functions are asynchronous and return a promise. Contains the following functions.
|
||||||
|
|
||||||
|
#### promisedAJAX
|
||||||
|
floBlockchainAPI.promisedAJAX(method, uri)
|
||||||
|
`promisedAJAX` resolves a responce from server on success or rejects the responce on failure.
|
||||||
|
1. method - GET/POST
|
||||||
|
- GET - Requests data from a specified resource.
|
||||||
|
- POST - Submits data to be processed to a specified resource.
|
||||||
|
2. uri(Uniform Resource Identifier) - identifier for AJAX resource. It is used to create URL(Uniform Resource Locator) for further operations.
|
||||||
|
|
||||||
|
#### getBalalnce
|
||||||
|
floBlockchainAPI.getBalance(addr)
|
||||||
|
`getBalance` resolves balance for specified FLO address.
|
||||||
|
1. addr - FLO address for which balance has to be retrieved.
|
||||||
|
|
||||||
|
#### writeData
|
||||||
|
floBlockchainAPI.writeData(senderAddr, Data, PrivKey, receiverAddr = floGlobals.adminID)
|
||||||
|
`writeData` writes data into blockchain.
|
||||||
|
1. senderAddr - FLO address from which the data and amount has to be sent.
|
||||||
|
2. Data - Actual FLO data that will be sent as string of 1040 characters.
|
||||||
|
3. receiverAddr - FLO address to which has to be sent. Default is specified in floGlobals.adminID.
|
||||||
|
4. PrivKey - Private key of sender to verify sender.
|
||||||
|
|
||||||
|
#### sendTx
|
||||||
|
floBlockchainAPI.sendTx(senderAddr, receiverAddr, sendAmt, PrivKey, floData = '')
|
||||||
|
`sendTx` sends a transaction to blockchain, resolves transaction id if the transacation was succsessful.
|
||||||
|
1. senderAddr - FLO address from which the data and amount has to be sent.
|
||||||
|
2. receiverAddr - FLO address to which has to be sent.
|
||||||
|
3. sendAmt - Amount of FLO coins to be sent to receiver.
|
||||||
|
4. PrivKey - Private key of sender to verify sender.
|
||||||
|
5. floData - Actual FLO data that will be sent as string of 1040 characters.
|
||||||
|
|
||||||
|
#### readTxs
|
||||||
|
floBlockchainAPI.readTxs(addr, from, to)
|
||||||
|
`readTxs` reads transactions of specified address between from and to.
|
||||||
|
1. addr - FLO address for which the transactions has to be read.
|
||||||
|
2. from - Reading transactions starts from 'from'.
|
||||||
|
3. to - Reading transactions ends on 'to'.
|
||||||
|
|
||||||
|
#### readAllTxs
|
||||||
|
floBlockchainAPI.readTxs(addr)
|
||||||
|
`readAllTxs` reads all transactions of specified address(newest first).
|
||||||
|
1. addr - FLO address for which the transactions has to be read.
|
||||||
|
|
||||||
|
#### readData
|
||||||
|
floBlockchainAPI.readData(addr, options = {})
|
||||||
|
`readData` reads FLO data from transactions of specified address
|
||||||
|
1. addr - FLO address for which the transactions data has to be read.
|
||||||
|
2. options - Contains options for filter data from transactions.
|
||||||
|
- limit : maximum number of filtered data (default = 1000, negative = no limit)
|
||||||
|
- ignoreOld : ignore old transactions (default = 0)
|
||||||
|
- sentOnly : filters only sent data
|
||||||
|
- pattern : filters data that starts with a pattern
|
||||||
|
- contains : filters data that contains a string
|
||||||
|
- filter : custom filter funtion for floData (eg . filter: d => {return d[0] == '$'})
|
||||||
|
|
||||||
|
## Compact IndexedDB operations
|
||||||
|
`compactIDB` operations can be used to perform basic IndexedDB operations such as add, read/write, modify and remove.Contains following operations.
|
||||||
|
|
||||||
|
#### setDefaultDB
|
||||||
|
compactIDB.setDefaultDB(dbName)
|
||||||
|
`setDefaultDB` sets the database on which further operations will be performed.
|
||||||
|
1. dbName - This is the name of default database to be used.
|
||||||
|
|
||||||
|
#### initDB
|
||||||
|
compactIDB.initDB(dbName, objectStores = {})
|
||||||
|
`initDB` initializes new IndexedDB.
|
||||||
|
1. dbName - Specifies database to be initialized.
|
||||||
|
2. objectStores - This is an object containing various objectStores to be initiazed when creating an IDB.
|
||||||
|
|
||||||
|
#### openDB
|
||||||
|
compactIDB.openDB(dbName)
|
||||||
|
`openDB` returns a promise that resolves to a default database object.
|
||||||
|
|
||||||
|
#### deleteDB
|
||||||
|
compactIDB.deleteDB(dbName)
|
||||||
|
`deleteDB` deletes the specified database.
|
||||||
|
|
||||||
|
#### writeData
|
||||||
|
compactIDB.writeData(obsName, data)
|
||||||
|
`writeData` writes specified data into the database if data doesn't exists or replaces it when data is already present.
|
||||||
|
1. obsName - object store name in which the data is to be written.
|
||||||
|
2. data - data that has to be written in specified object store.
|
||||||
|
|
||||||
|
#### addData
|
||||||
|
compactIDB.addData(obsName, data)
|
||||||
|
`addData` writes new data into object store. If data already exists, it will return an error.
|
||||||
|
1. obsName - Object store name in which has to be stored.
|
||||||
|
2. data - The data which has to be added to obeject store.
|
||||||
|
|
||||||
|
#### removeData
|
||||||
|
compactDB.removeData(obsName, key)
|
||||||
|
`removeData` deletes data from specified object store using primary key.
|
||||||
|
1. obsName - Name of object store from which the data has to be removed.
|
||||||
|
2. key - Primary key of the specified object store.
|
||||||
|
|
||||||
|
#### readData
|
||||||
|
compactDB.readData(obsName, key)
|
||||||
|
`readData` reads the data from object store associated with specified key.
|
||||||
|
1. obsName - Name of object store from which the data has to be retrieved.
|
||||||
|
2. key - 2.key - Primary key of the specified object store.
|
||||||
|
|
||||||
|
#### readAllData
|
||||||
|
compactDB.readAllData(obsName)
|
||||||
|
`readAllData` reads all the data from specified object store using IndexedDB openCursor method.
|
||||||
|
1. obsName - Name of object store from which the data has to be retrieved.
|
||||||
|
`signData` signs the data using the private key
|
||||||
|
1. data - data to sign
|
||||||
|
2. privateKey - private key of the signer
|
||||||
|
|
||||||
|
|||||||
@ -10,12 +10,12 @@
|
|||||||
const floGlobals = {
|
const floGlobals = {
|
||||||
|
|
||||||
//Required for all
|
//Required for all
|
||||||
blockchain: "FLO_TEST",
|
blockchain: "FLO",
|
||||||
|
|
||||||
//Required for blockchain API operators
|
//Required for blockchain API operators
|
||||||
apiURL: {
|
apiURL: {
|
||||||
FLO: 'https://flosight.duckdns.org',
|
FLO: ['https://explorer.mediciland.com/', 'https://livenet.flocha.in/', 'https://flosight.duckdns.org/', 'http://livenet-explorer.floexperiments.com', 'http://ec2-13-233-133-128.ap-south-1.compute.amazonaws.com/', 'http://ec2-13-233-131-136.ap-south-1.compute.amazonaws.com/', 'http://ec2-13-233-194-1.ap-south-1.compute.amazonaws.com/'],
|
||||||
FLO_TEST: 'https://testnet-flosight.duckdns.org'
|
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/']
|
||||||
},
|
},
|
||||||
adminID: "oTZw3ydCRKDhcYC5Bp6mRJMGTTVv9JHtg8",
|
adminID: "oTZw3ydCRKDhcYC5Bp6mRJMGTTVv9JHtg8",
|
||||||
sendAmt: 0.001,
|
sendAmt: 0.001,
|
||||||
@ -4692,7 +4692,6 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* Reactor Event handling */
|
/* Reactor Event handling */
|
||||||
if (typeof reactor == "undefined" || !reactor) {
|
if (typeof reactor == "undefined" || !reactor) {
|
||||||
@ -4974,44 +4973,75 @@
|
|||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
|
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
|
||||||
|
|
||||||
|
const getRandomInt = function(min, max) {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
const floBlockchainAPI = {
|
const floBlockchainAPI = {
|
||||||
|
|
||||||
//Promised AJAX function to get data from API
|
util: {
|
||||||
promisedAJAX: function (method, uri) {
|
serverList: floGlobals.apiURL[floGlobals.blockchain].slice(0),
|
||||||
|
curPos: getRandomInt(0, floGlobals.apiURL[floGlobals.blockchain].length),
|
||||||
|
fetch_retry: function(apicall){
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
this.serverList.splice(this.curPos, 1);
|
||||||
|
this.curPos = getRandomInt(0, this.serverList.length)
|
||||||
|
this.fetch_api(apicall)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fetch_api: function(apicall){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(this.serverList.length === 0)
|
||||||
|
reject("No floSight server working")
|
||||||
|
else{
|
||||||
|
fetch(this.serverList[this.curPos] + apicall).then(response => {
|
||||||
|
if(response.ok)
|
||||||
|
response.json().then(data => resolve(data));
|
||||||
|
else{
|
||||||
|
this.fetch_retry(apicall)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
this.fetch_retry(apicall)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//Promised function to get data from API
|
||||||
|
promisedAPI: function (apicall) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var request = new XMLHttpRequest();
|
console.log(apicall)
|
||||||
var url = `${floGlobals.apiURL[floGlobals.blockchain]}/${uri}`;
|
this.util.fetch_api(apicall)
|
||||||
console.log(url)
|
.then(result => resolve(result))
|
||||||
request.open(method, url, true);
|
.catch(error => reject(error));
|
||||||
request.onload = (evt) => {
|
|
||||||
if (request.readyState == 4 && request.status == 200)
|
|
||||||
resolve(request.response);
|
|
||||||
else
|
|
||||||
reject(request.response);
|
|
||||||
};
|
|
||||||
request.send();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
//Get balance for the given Address
|
//Get balance for the given Address
|
||||||
getBalance: function (addr) {
|
getBalance: function (addr) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.promisedAJAX("GET", `api/addr/${addr}/balance`).then(balance => {
|
this.promisedAPI(`api/addr/${addr}/balance`)
|
||||||
resolve(parseFloat(balance));
|
.then(balance => resolve(parseFloat(balance)))
|
||||||
}).catch(error => {
|
.catch(error => reject(error));
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
//Write Data into blockchain
|
//Write Data into blockchain
|
||||||
writeData: function (senderAddr, Data, PrivKey, receiverAddr = floGlobals.adminID) {
|
writeData: function (senderAddr, Data, PrivKey, receiverAddr = floGlobals.adminID) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.sendTx(senderAddr, receiverAddr, floGlobals.sendAmt, PrivKey, Data).then(txid => {
|
this.sendTx(senderAddr, receiverAddr, floGlobals.sendAmt, PrivKey, Data)
|
||||||
resolve(txid);
|
.then(txid => resolve(txid))
|
||||||
}).catch(error => {
|
.catch(error => reject(error))
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -5030,8 +5060,7 @@
|
|||||||
var trx = bitjs.transaction();
|
var trx = bitjs.transaction();
|
||||||
var utxoAmt = 0.0;
|
var utxoAmt = 0.0;
|
||||||
var fee = floGlobals.fee;
|
var fee = floGlobals.fee;
|
||||||
this.promisedAJAX("GET", `api/addr/${senderAddr}/utxo`).then(response => {
|
this.promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => {
|
||||||
var utxos = JSON.parse(response);
|
|
||||||
for (var i = utxos.length - 1;
|
for (var i = utxos.length - 1;
|
||||||
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
|
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
|
||||||
if (utxos[i].confirmations) {
|
if (utxos[i].confirmations) {
|
||||||
@ -5048,15 +5077,11 @@
|
|||||||
trx.addoutput(senderAddr, change);
|
trx.addoutput(senderAddr, change);
|
||||||
trx.addflodata(floData);
|
trx.addflodata(floData);
|
||||||
var signedTxHash = trx.sign(PrivKey, 1);
|
var signedTxHash = trx.sign(PrivKey, 1);
|
||||||
this.broadcastTx(signedTxHash).then(txid => {
|
this.broadcastTx(signedTxHash)
|
||||||
resolve(txid)
|
.then(txid => resolve(txid))
|
||||||
}).catch(error => {
|
.catch(error => reject(error))
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => reject(error))
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -5065,7 +5090,7 @@
|
|||||||
broadcastTx: function (signedTxHash) {
|
broadcastTx: function (signedTxHash) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
var url = `${floGlobals.apiURL[floGlobals.blockchain]}/api/tx/send`;
|
var url = this.util.serverList[this.util.curPos] + 'api/tx/send';
|
||||||
if (signedTxHash.length < 1)
|
if (signedTxHash.length < 1)
|
||||||
reject("Empty Signature");
|
reject("Empty Signature");
|
||||||
else {
|
else {
|
||||||
@ -5088,27 +5113,20 @@
|
|||||||
//Read Txs of Address between from and to
|
//Read Txs of Address between from and to
|
||||||
readTxs: function (addr, from, to) {
|
readTxs: function (addr, from, to) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=${from}&to=${to}`).then(response => {
|
this.promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`)
|
||||||
resolve(JSON.parse(response));
|
.then(response => resolve(response))
|
||||||
}).catch(error => {
|
.catch(error => reject(error))
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
//Read All Txs of Address (newest first)
|
//Read All Txs of Address (newest first)
|
||||||
readAllTxs: function (addr) {
|
readAllTxs: function (addr) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
this.promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
||||||
var totalItems = JSON.parse(response).totalItems;
|
this.promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`)
|
||||||
this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=${totalItems}0`).then(response => {
|
.then(response => resolve(response.items))
|
||||||
resolve(JSON.parse(response).items);
|
.catch(error => reject(error));
|
||||||
}).catch(error => {
|
}).catch(error => reject(error))
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -5125,10 +5143,9 @@
|
|||||||
options.limit = options.limit | 1000
|
options.limit = options.limit | 1000
|
||||||
options.ignoreOld = options.ignoreOld | 0
|
options.ignoreOld = options.ignoreOld | 0
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
this.promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
||||||
var newItems = JSON.parse(response).totalItems - options.ignoreOld;
|
var newItems = response.totalItems - options.ignoreOld;
|
||||||
this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=${newItems*2}`).then(response => {
|
this.promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems*2}`).then(response => {
|
||||||
response = JSON.parse(response)
|
|
||||||
if (options.limit <= 0)
|
if (options.limit <= 0)
|
||||||
options.limit = response.items.length;
|
options.limit = response.items.length;
|
||||||
var filteredData = [];
|
var filteredData = [];
|
||||||
@ -5218,6 +5235,50 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getPrevSupernode: function(flo_addr, n = 1, KB = this.supernodeKBucket){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let isFloIdUint8 = flo_addr instanceof Uint8Array;
|
||||||
|
if (!isFloIdUint8)
|
||||||
|
flo_addr = this.floIdToKbucketId(flo_addr);
|
||||||
|
const KA = KB.toArray();
|
||||||
|
let pos = KB._indexOf(KB.root,flo_addr)
|
||||||
|
var prevSupernode = []
|
||||||
|
for(var i = 1; i <= n; i++){
|
||||||
|
if(pos - i < 0)
|
||||||
|
var prev = pos - i + KA.length
|
||||||
|
else
|
||||||
|
var prev = pos - i
|
||||||
|
prevSupernode.push(KA[prev])
|
||||||
|
}
|
||||||
|
resolve(prevSupernode);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getNextSupernode: function(flo_addr, n = 1, KB = this.supernodeKBucket){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let isFloIdUint8 = flo_addr instanceof Uint8Array;
|
||||||
|
if (!isFloIdUint8)
|
||||||
|
flo_addr = this.floIdToKbucketId(flo_addr);
|
||||||
|
const KA = KB.toArray();
|
||||||
|
let pos = KB._indexOf(KB.root,flo_addr)
|
||||||
|
var nextSupernode = []
|
||||||
|
for(var i = 1; i <= n; i++){
|
||||||
|
if(pos + i >= KA.length)
|
||||||
|
var next = pos + i - KA.length
|
||||||
|
else
|
||||||
|
var next = pos + i
|
||||||
|
nextSupernode.push(KA[next])
|
||||||
|
}
|
||||||
|
resolve(nextSupernode);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
determineClosestSupernode: function (flo_addr, n = 1, KB = this.supernodeKBucket) {
|
determineClosestSupernode: function (flo_addr, n = 1, KB = this.supernodeKBucket) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
@ -5233,42 +5294,78 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sendDataToSN(data, snfloID){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log(snfloID)
|
||||||
|
var websocket = new WebSocket("wss://" + floGlobals.supernodes[snfloID].uri + "/ws");
|
||||||
|
websocket.onmessage = (evt => {
|
||||||
|
if(evt.data == '$+'){
|
||||||
|
websocket.send(data);
|
||||||
|
resolve(`Data sent to supernode : ${snfloID}`);
|
||||||
|
}else if(evt.data == '$-'){
|
||||||
|
this.kBucket.getNextSupernode(snfloID)
|
||||||
|
.then(nextnode => this.sendDataToSN(data, nextnode[0].floID))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}else{
|
||||||
|
console.log(evt.data)
|
||||||
|
reject(evt.data)
|
||||||
|
}
|
||||||
|
websocket.close();
|
||||||
|
})
|
||||||
|
websocket.onerror = (evt) => {
|
||||||
|
this.kBucket.getNextSupernode(snfloID)
|
||||||
|
.then(nextnode => this.sendDataToSN(data, nextnode[0].floID))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
};
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
//Sends data to the supernode
|
//Sends data to the supernode
|
||||||
sendData: function (data, floID) {
|
sendData: function (data, floID) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.kBucket.determineClosestSupernode(floID).then(result => {
|
this.kBucket.determineClosestSupernode(floID).then(result => {
|
||||||
var websocket = new WebSocket("wss://" + floGlobals.supernodes[result[0].floID].uri + "/ws");
|
this.sendDataToSN(data, result[0].floID)
|
||||||
websocket.onopen = (evt) => {
|
.then(result => resolve(result))
|
||||||
websocket.send(data);
|
.catch(error => reject(error))
|
||||||
resolve(`Data sent to ${floID}'s supernode`);
|
|
||||||
websocket.close();
|
|
||||||
};
|
|
||||||
websocket.onerror = (evt) => {
|
|
||||||
reject(evt);
|
|
||||||
};
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
requestDataFromSN(request, snfloID){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var websocket = new WebSocket("wss://" + floGlobals.supernodes[snfloID].uri + "/ws");
|
||||||
|
websocket.onmessage = (evt => {
|
||||||
|
if(evt.data == '$+'){
|
||||||
|
websocket.send(`?${request}`);
|
||||||
|
}else if(evt.data == '$-'){
|
||||||
|
this.kBucket.getNextSupernode(snfloID)
|
||||||
|
.then(nextnode => this.requestDataFromSN(request, nextnode[0].floID))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
websocket.close()
|
||||||
|
}else{
|
||||||
|
resolve(evt.data);
|
||||||
|
websocket.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
websocket.onerror = (evt) => {
|
||||||
|
this.kBucket.getNextSupernode(snfloID)
|
||||||
|
.then(nextnode => this.requestDataFromSN(request, nextnode[0].floID))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
};
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
//Request data from supernode
|
//Request data from supernode
|
||||||
requestData: function (request, floID) {
|
requestData: function (request, floID) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.kBucket.determineClosestSupernode(floID).then(result => {
|
this.kBucket.determineClosestSupernode(floID).then(result => {
|
||||||
var websocket = new WebSocket("wss://" + floGlobals.supernodes[result[0].floID].uri + "/ws");
|
this.requestDataFromSN(request, result[0].floID)
|
||||||
websocket.onopen = (evt) => {
|
.then(result => resolve(result))
|
||||||
websocket.send(`?${request}`);
|
.catch(error => reject(error))
|
||||||
};
|
|
||||||
websocket.onmessage = (evt) => {
|
|
||||||
resolve(evt.data);
|
|
||||||
websocket.close();
|
|
||||||
};
|
|
||||||
websocket.onerror = (evt) => {
|
|
||||||
reject(evt);
|
|
||||||
};
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -5834,5 +5931,412 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
//For diff base
|
||||||
|
/*
|
||||||
|
Functions available:
|
||||||
|
diff(originalObj, updatedObj) returns the difference of the original and updated objects
|
||||||
|
addedDiff(original, updatedObj) returns only the values added to the updated object
|
||||||
|
deletedDiff(original, updatedObj) returns only the values deleted in the updated object
|
||||||
|
updatedDiff(original, updatedObj) returns only the values that have been changed in the updated object
|
||||||
|
detailedDiff(original, updatedObj) returns an object with the added, deleted and updated differences
|
||||||
|
mergeRecurcive(original, diff) this will get you a new object that will merge all the changes between the old object and the new object
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 detailedDiff = (lhs, rhs) => ({
|
||||||
|
added: addedDiff(lhs, rhs),
|
||||||
|
deleted: deletedDiff(lhs, rhs),
|
||||||
|
updated: updatedDiff(lhs, rhs),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 };
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
function 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
var test = {
|
||||||
|
foo : {
|
||||||
|
bar : {
|
||||||
|
baz : null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bar : 1
|
||||||
|
};
|
||||||
|
cleanse(test);
|
||||||
|
|
||||||
|
Rohit: Added a small fix for object being entered as Array*/
|
||||||
|
|
||||||
|
function cleanse(obj) {
|
||||||
|
console.log(obj)
|
||||||
|
Object.keys(obj).forEach(function(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) {
|
||||||
|
// null, remove it
|
||||||
|
delete obj[key];
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(obj.constructor.toString().indexOf("Array") != -1) {obj = obj.filter(function (el) {
|
||||||
|
return el != null;
|
||||||
|
});}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*obj is original object or array, diff is the output of detailedDiff */
|
||||||
|
function mergeAllDiff(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
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
/* FLO Cloud operations to send/request application data*/
|
||||||
|
floCloudAPI = {
|
||||||
|
//send Any message to supernode cloud storage
|
||||||
|
sendApplicationData: function(message, type, options = {}){
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
var data = {
|
||||||
|
senderID : myFloID,
|
||||||
|
receiverID: options.receiverID || floGlobals.adminID,
|
||||||
|
pubKey : myPubKey,
|
||||||
|
message : message,
|
||||||
|
sign: floCrypto.signData(JSON.stringify(message), myPrivKey),
|
||||||
|
application : options.application || floGlobals.application,
|
||||||
|
type: type,
|
||||||
|
comment: options.comment || ""
|
||||||
|
}
|
||||||
|
floSupernode.sendData(JSON.stringify(data), data.receiverID)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
//request an object data from supernode cloud
|
||||||
|
requestObjectData: function(objectName, options = {}){
|
||||||
|
var request = {
|
||||||
|
receiverID: options.receiverID || floGlobals.adminID,
|
||||||
|
senderIDs: options.senderIDs || floGlobals.subAdmins,
|
||||||
|
application: options.application || floGlobals.application,
|
||||||
|
type: `${objectName}@Reset`,
|
||||||
|
lowerVectorClock: floGlobals.vectorClock[objectName]+1,
|
||||||
|
mostRecent: true
|
||||||
|
}
|
||||||
|
floSupernode.requestData(JSON.stringify(request),request.receiverID).then(resetData => {
|
||||||
|
reactor.dispatchEvent('resetData', resetData);
|
||||||
|
request.type = `${objectName}@Update`
|
||||||
|
request.lowerVectorClock = floGlobals.vectorClock[objectName]+1
|
||||||
|
request.mostRecent = false
|
||||||
|
floSupernode.requestData(JSON.stringify(request), request.receiverID)
|
||||||
|
.then(updateData => reactor.dispatchEvent('updateData', updateData))
|
||||||
|
}).catch(error => reactor.dispatchEvent('errorHandle', error))
|
||||||
|
},
|
||||||
|
|
||||||
|
//reset or initialize an object and send it to cloud
|
||||||
|
resetObjectData: function(newObject, objectName, options = {}){
|
||||||
|
var message = {
|
||||||
|
division: options.division || floGlobals.division,
|
||||||
|
object: objectName,
|
||||||
|
vectorClock: floGlobals.vectorClock[object],
|
||||||
|
reset: newObject
|
||||||
|
}
|
||||||
|
this.sendApplicationData(message, `${object}@Reset`, options)
|
||||||
|
.then(result => reactor.dispatchEvent('logHandle',result))
|
||||||
|
.catch(error => reactor.dispatchEvent('errorHandle',error))
|
||||||
|
},
|
||||||
|
|
||||||
|
//update the diff and send it to cloud
|
||||||
|
updateObjectData: function(oldObject, newObject, objectName, options = {}){
|
||||||
|
var message = {
|
||||||
|
division: options.division || floGlobals.division,
|
||||||
|
object: objectName,
|
||||||
|
vectorClock: floGlobals.vectorClock[object],
|
||||||
|
diff: detailedDiff(oldObject, newObject)
|
||||||
|
}
|
||||||
|
this.sendApplicationData(message, `${object}@Update`, options)
|
||||||
|
.then(result => reactor.dispatchEvent('logHandle',result))
|
||||||
|
.catch(error => reactor.dispatchEvent('errorHandle',error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reactor.registerEvent('resetData');
|
||||||
|
reactor.addEventListener('resetData', function(dataSet){
|
||||||
|
try{
|
||||||
|
dataSet = JSON.parse(dataSet);
|
||||||
|
console.log(dataSet)
|
||||||
|
for(vc in dataSet){
|
||||||
|
if(floGlobals.subAdmins.includes(dataSet[v].senderID)){
|
||||||
|
var message = dataSet[v].message;
|
||||||
|
if(message.reset){
|
||||||
|
floGlobals.appObjects[message.object] = message.reset
|
||||||
|
floGlobals.vectorClock[message.object] = vc
|
||||||
|
//compactIDB.writeData("appObjects", floGlobals.appObjects[message.object], message.object)
|
||||||
|
//compactIDB.writeData("vectorClocks", vc, message.object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(error){
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reactor.registerEvent('updateData');
|
||||||
|
reactor.addEventListener('updateData', function(dataSet){
|
||||||
|
try{
|
||||||
|
dataSet = JSON.parse(dataSet);
|
||||||
|
console.log(dataSet)
|
||||||
|
for(vc in dataSet){
|
||||||
|
if(floGlobals.subAdmins.includes(dataSet[v].senderID)){
|
||||||
|
var message = dataSet[v].message;
|
||||||
|
if(message.diff){
|
||||||
|
floGlobals.appObjects[message.object] = mergeAllDiff(floGlobals.appObjects[message.object], message.diff)
|
||||||
|
//compactIDB.writeData("objects",floGlobals.appObjects[message.object], message.object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
floGlobals.vectorClock[message.object] = vc
|
||||||
|
//compactIDB.writeData("vectorClocks", vc, message.object)
|
||||||
|
}
|
||||||
|
}catch(error){
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reactor.registerEvent('errorHandle');
|
||||||
|
reactor.addEventListener('errorHandle', function(event){
|
||||||
|
console.log(event)
|
||||||
|
});
|
||||||
|
reactor.registerEvent('logHandle');
|
||||||
|
reactor.addEventListener('logHandle', function(event){
|
||||||
|
console.log(event)
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user