Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20ae09fba9 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
*.tmp*
|
|
||||||
21
LICENCE
21
LICENCE
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2023 Sai Raj
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
648
README.md
648
README.md
@ -1,4 +1,4 @@
|
|||||||
# FLO Standard Operations
|
# Standard_Operations
|
||||||
RanchiMall is releasing FLO Standard Operations in order to make it easier for everyone in community to access common functions that any developer would need in a standardized way for JavaScript based development around FLO Blockchain ecosystem.
|
RanchiMall is releasing FLO Standard Operations in order to make it easier for everyone in community to access common functions that any developer would need in a standardized way for JavaScript based development around FLO Blockchain ecosystem.
|
||||||
|
|
||||||
The methods enable developers to read and write into FLO Blockchain directly without any wallet or Chrome extension dependencies. They also enable developers to create digital signatures for any message out of any FLO ID, and functions verify them as well. In additional, standard methods are being provided for data encryption using recipient FLO ID and private key splitting using Shamir Secret Sharing algorithm.
|
The methods enable developers to read and write into FLO Blockchain directly without any wallet or Chrome extension dependencies. They also enable developers to create digital signatures for any message out of any FLO ID, and functions verify them as well. In additional, standard methods are being provided for data encryption using recipient FLO ID and private key splitting using Shamir Secret Sharing algorithm.
|
||||||
@ -9,21 +9,18 @@ We are offering methods simplifying access to inbuilt browser database IndexedDB
|
|||||||
|
|
||||||
Last but not the least, we are also providing methods for simplying common operations for FLO based Distributed Application Development.
|
Last but not the least, we are also providing methods for simplying common operations for FLO based Distributed Application Development.
|
||||||
|
|
||||||
# IMPORTANT
|
|
||||||
We have two versions of cloud: old cloud version is 2.0.x in floCloudAPI, and new cloud version is >2.1.0 in floCloudAPI. Please check that the version in floCloudAPI is >2.1.0 whenever you use floCloudAPI as we are deprecating version 2.0.x
|
|
||||||
|
|
||||||
# Background on FLO Distributed Applications
|
# Background on FLO Distributed Applications
|
||||||
|
|
||||||
FLO distibuted applications use the FLO Blockchain to store core data, and FLO Blockchain Cloud for other data. The data is accessed directly by browser based clients. No other component or servers are needed typically.
|
FLO distibuted applications use the FLO Blockchain to store core data, and FLO Blockchain Cloud for other data. The data is accessed directly by browser based clients. No other component or servers are needed typically.
|
||||||
|
|
||||||
Every application has a role called the Master Admin. The clients trust anything that the Master Admin declares in the FLO Blockchain. Usually Master Admin will only declare the FLO IDs that will have operational roles in the application. These FLO IDs are called SubAdmins. The browser based clients trust actions only and only from subAdmins and no one else. That creates the trust model. First trust the Master Admin. Then find out who the master admin has authorized to act as SubAdmins in the FLO blockchain. Then trust the data and actions signed by approved SubAdmins. Since the entire chain of trust is blockchain based, it enables a blockchain driven trust model. As the blockchain data is immutable and permanent, so long as users can trust the Master Admin, blockchain will ensure that the trust is efficiently transmitted.
|
Every application has a role called the Master Admin. The clients trust anything that the Master Admin declares in the FLO Blockchain. Usually Master Admin will only declare the FLO IDs that will have operational roles in the application. These FLO IDs are called SubAdmins. The browser based clients trust actions only and only from subAdmins and no one else. That creates the trust model. First trust the Master Admin. Then find out who the master admin has authorized to act as SubAdmins in the FLO blockchain. Then trust the data and actions signed by approved SubAdmins. Since the entire chain of trust is blockchain based, it enables a blockchain driven trust model. As the blockchain data is immutable and permanent, so long as users can trust the Master Admin, blockchain will ensure that the turst is efficiently transmitted.
|
||||||
|
|
||||||
This approach decentralizes the trust process totally and extends the capacity of the blockchain to model almost any server based IT application.
|
This approach decentralizes the trust process totally and extends the capacity of the blockchain to model almost any server based IT application.
|
||||||
|
|
||||||
## RanchiMall recommended approach to create a FLO Distributed Application
|
## RanchiMall recommended approach to create a FLO Distributed Application
|
||||||
|
|
||||||
1. Create Master Admin FLO ID and private key
|
1. Create Master Admin FLO ID and private key
|
||||||
2. Role Modeling: Create SubAdmins by having Master Admin declare it in the FLO Blockchain, and decide what roles will different kind of SubAdmins play
|
2. Role Modeling: Create SubAdmins by having Master Admin declare it in the FLO Blockchain, and decide what roles will differnent kind of SubAdmins play
|
||||||
3. Data Modeling: Create Blockchain cloud data formats for your application. Do it twice: One for system trusted users like SubAdmins using Object Data. And again for normal untrusted users using General Data
|
3. Data Modeling: Create Blockchain cloud data formats for your application. Do it twice: One for system trusted users like SubAdmins using Object Data. And again for normal untrusted users using General Data
|
||||||
4. Define core business functionalities of the app, and create Javascript methods to model it
|
4. Define core business functionalities of the app, and create Javascript methods to model it
|
||||||
5. Secure the user Private Key (and other sensitive data, if any)
|
5. Secure the user Private Key (and other sensitive data, if any)
|
||||||
@ -62,101 +59,16 @@ This approach decentralizes the trust process totally and extends the capacity o
|
|||||||
|
|
||||||
* Consumers of data can ask for data by receiver ID, or filter it by application, type, or comment. They can also ask for data for a given type before and after a certain vector clock.
|
* Consumers of data can ask for data by receiver ID, or filter it by application, type, or comment. They can also ask for data for a given type before and after a certain vector clock.
|
||||||
|
|
||||||
## Common examples in usage of Standard Operations
|
|
||||||
|
|
||||||
* Sends "Hello World" message to the cloud as General Data with type1 as `type` with `myFloID` as default sender and `floGlobals.adminID` as receiver. This has vectorClock support, our the client side will automatically synchronise all the relevant data stored in cloud.
|
|
||||||
```
|
|
||||||
floCloudAPI.sendGeneralData("Hello World", "type1")
|
|
||||||
```
|
|
||||||
|
|
||||||
* Request general data of type1 type, but filtered by senderIDs being floGlobals.subAdmins
|
|
||||||
```
|
|
||||||
floCloudAPI.requestGeneralData("type1", { senderIDs: floGlobals.subAdmins })
|
|
||||||
```
|
|
||||||
|
|
||||||
* Usage of sendGeneralData from RIBC dApp
|
|
||||||
```
|
|
||||||
//take name and comments as function parameters, and sendGeneralData of InternRequests type
|
|
||||||
applyForIntern: function (name, comments = '') {
|
|
||||||
return floCloudAPI.sendGeneralData([name, comments], "InternRequests")
|
|
||||||
},
|
|
||||||
|
|
||||||
//take updates as function parameter, and return a promise that sends general data of type "InternUpdates"
|
|
||||||
postInternUpdate: function (updates) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
floCloudAPI.sendGeneralData(updates, "InternUpdates")
|
|
||||||
.then(results => resolve(results))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
})
|
|
||||||
|
|
||||||
applyForTask: function (projectCode, branch, task, comments = '') {
|
|
||||||
return floCloudAPI.sendGeneralData([projectCode, branch, task, comments], "TaskRequests")
|
|
||||||
},
|
|
||||||
|
|
||||||
setRequestStatus: function (vectorClock, status) {
|
|
||||||
return floCloudAPI.sendGeneralData({ vectorClock, status }, "RequestStatus")
|
|
||||||
},
|
|
||||||
```
|
|
||||||
* Object Data handling is automated in Standard operations unlike General data. Define, update and reset, these are only object data operations possible, yet they are very powerful in data capabilities.
|
|
||||||
```
|
|
||||||
//Initiates "myFirstObject" with {a:1,b:2}, and sends to cloud with myFloID
|
|
||||||
as default sender and floGlobals.adminID as receiver
|
|
||||||
|
|
||||||
floGlobals.appObjects["myFirstObject"] = {a:1,b:2}
|
|
||||||
floCloudAPI.resetObjectData("myFirstObject")
|
|
||||||
|
|
||||||
//Updates the old value of "myFirstObject" with {a:1,b:2}, and sends to cloud with myFloID as default sender
|
|
||||||
and floGlobals.adminID as receiver. In case of update, only the object diff will be sent
|
|
||||||
|
|
||||||
floGlobals.appObjects["myFirstObject"] = {a:1,c:3,d:4}
|
|
||||||
floCloudAPI.updateObjectData("myFirstObject")
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
* floGlobals usage from RIBC dApp
|
|
||||||
```
|
|
||||||
|
|
||||||
floGlobals.vectorClock
|
|
||||||
= {RIBC: '1637841611864_FCja6sLv58e3RMy41T5AmWyvXEWesqBCkX'}
|
|
||||||
|
|
||||||
floGlobals.generalVC["{\"application\":\"RIBC\",\"type\":\"InternUpdates\"}"]
|
|
||||||
= '1640106537305_FSdjJCJdU43a1dyWY6dRES1ekoupEjFPqQ'
|
|
||||||
|
|
||||||
floGlobals.vectorClock.RIBC
|
|
||||||
= '1637841611864_FCja6sLv58e3RMy41T5AmWyvXEWesqBCkX'
|
|
||||||
|
|
||||||
floGlobals.generalVC["{\"application\":\"RIBC\",\"type\":\"InternUpdates\"}"]
|
|
||||||
= '1640106537305_FSdjJCJdU43a1dyWY6dRES1ekoupEjFPqQ'
|
|
||||||
|
|
||||||
floGlobals.generalData["{\"application\":\"RIBC\",\"type\":\"InternUpdates\"}"][2]
|
|
||||||
= {sender: 'FPFeL5PXzW9bGosUjQYCxTHSMHidnygvvd',
|
|
||||||
vectorClock: '1580815876258_FPFeL5PXzW9bGosUjQYCxTHSMHidnygvvd', message: {…}}
|
|
||||||
|
|
||||||
floGlobals.generalData["{\"application\":\"RIBC\",\"type\":\"InternUpdates\"}"][2]["vectorClock"]
|
|
||||||
= '1580815876258_FPFeL5PXzW9bGosUjQYCxTHSMHidnygvvd'
|
|
||||||
|
|
||||||
floGlobals.generalData["{\"application\":\"RIBC\",\"type\":\"InternUpdates\"}"][2]["message"]["sender"]
|
|
||||||
= 'FPFeL5PXzW9bGosUjQYCxTHSMHidnygvvd'
|
|
||||||
|
|
||||||
floGlobals.generalData["{\"application\":\"RIBC\",\"type\":\"InternUpdates\"}"][808]["message"]["description"]
|
|
||||||
= 'Working on the flow and structure of Pearl Harbor topic'
|
|
||||||
|
|
||||||
floGlobals.appObjects.RIBC.internList.F7HVKrF68Y6YKE9XXpHhAcxt6MwRLcUD67
|
|
||||||
= 'Salomi Sarkar'
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Technical Details of standard operations
|
# Technical Details of standard operations
|
||||||
|
|
||||||
This template contains standard operations that can be used for the following:
|
This template contains standard operations that can be used for the following:
|
||||||
| index |
|
1. FLO Globals for system variables and data objects users must configure
|
||||||
| --- |
|
2. FLO Crypto Operations
|
||||||
| [FLO Globals](#flo-globals) |
|
3. FLO Blockchain API Operations
|
||||||
| [FLO Crypto Operations](#flo-crypto-operations) |
|
4. Compact IndexedDB Operations
|
||||||
| [FLO Blockchain API Operations](#flo-blockchain-api-operations) |
|
5. FLO Cloud API Operations
|
||||||
| [Compact IndexedDB Operations](#compact-indexeddb-operations) |
|
6. FLO Decentralized app (Dapp) Operations
|
||||||
| [FLO Cloud API Operations](#flo-cloud-api-operations) |
|
|
||||||
| [FLO Decentralized app (Dapp) Operations](#flo-decentralised-applications-dapps) |
|
|
||||||
|
|
||||||
## FLO Globals
|
## FLO Globals
|
||||||
`floGlobals` object contains the global variables and constants required for the operations. Make sure to add this object before any other scripts.
|
`floGlobals` object contains the global variables and constants required for the operations. Make sure to add this object before any other scripts.
|
||||||
@ -171,26 +83,18 @@ This template contains standard operations that can be used for the following:
|
|||||||
7. `SNStorageID`* : SuperNode Storage adminID.
|
7. `SNStorageID`* : SuperNode Storage adminID.
|
||||||
8. `supernodes`⁺ : List of supernodes.
|
8. `supernodes`⁺ : List of supernodes.
|
||||||
9. `subAdmins`⁺ : subAdmins of the application.
|
9. `subAdmins`⁺ : subAdmins of the application.
|
||||||
10. `appObjects`⁺ : Object Data for the app (data sent by subAdmins).
|
10. `appData`⁺ : Application data for the app (data sent by subAdmins).
|
||||||
11. `generalData`⁺: General Data for the app (data sent by any user).
|
11. `generalData`⁺: General Data for the app (data sent by any user).
|
||||||
12. `vectorClock`⁺ and `generalVC`⁺ : vectorclocks for application data and general data respectively.
|
12. `vectorClock`⁺ and `generalVC`⁺ : vectorclocks for application data and general data respectively.
|
||||||
|
|
||||||
\* Values that should NOT be edited.
|
\* Values that shoult NOT be edited.
|
||||||
⁺ Values that updates in runtime.
|
⁺ Values that updates in runtime.
|
||||||
1-8 : Required for all applications.
|
1-8 : Required for all applications.
|
||||||
9-12: Required for applications based on floClouldAPI and floDapps module.
|
9-12: Required for applications based on floClouldAPI and floDapps module.
|
||||||
|
|
||||||
In addition, we have these system variables outside FLO Globals but used globally
|
|
||||||
1. `myFloID` : FLO ID with which user has logged in
|
|
||||||
2. `myPubKey` : Public Key attached to the user FLO ID
|
|
||||||
3. `myPrivKey` : Private Key corresponding to the user FLO ID
|
|
||||||
|
|
||||||
|
|
||||||
## FLO Crypto Operations
|
## FLO Crypto Operations
|
||||||
`floCrypto` operations can be used to perform blockchain-cryptography methods. `floCrypto` operations are synchronized and return a value. Contains the following Operations.
|
`floCrypto` operations can be used to perform blockchain-cryptography methods. `floCrypto` operations are synchronized and return a value. Contains the following Operations.
|
||||||
|
|
||||||
#### Important: FLO Crypto operations are all functions. They have not been promisified
|
|
||||||
|
|
||||||
#### Generate New FLO ID pair
|
#### Generate New FLO ID pair
|
||||||
floCrypto.generateNewID()
|
floCrypto.generateNewID()
|
||||||
`generateNewID` generates a new flo ID and returns private-key, public-key and floID
|
`generateNewID` generates a new flo ID and returns private-key, public-key and floID
|
||||||
@ -203,18 +107,11 @@ In addition, we have these system variables outside FLO Globals but used globall
|
|||||||
* Returns : pubKey (string)
|
* Returns : pubKey (string)
|
||||||
|
|
||||||
#### Calculate FLO ID
|
#### Calculate FLO ID
|
||||||
floCrypto.getFloID(publickey_or_privateKey)
|
floCrypto.getFloIDfromPubkeyHex(publicKey)
|
||||||
`getFloID` returns flo-ID from given public-key (or) private-key
|
`getFloIDfromPubkeyHex` returns flo-ID from public-key
|
||||||
1. publickey_or_privateKey - public key or private key hex value
|
1. publicKey - public key hex value
|
||||||
* Returns : floID (string)
|
* Returns : floID (string)
|
||||||
|
|
||||||
#### Calculate Address
|
|
||||||
floCrypto.getAddress(privateKey, *strict)
|
|
||||||
`getAddress` returns respective address from given private-key
|
|
||||||
1. privateKey - private key in WIF format
|
|
||||||
2. strict - boolean value (optional, default=false) (false: return flo-id if no prefix match is found)
|
|
||||||
* Returns : address (string)
|
|
||||||
|
|
||||||
#### Verify Private Key
|
#### Verify Private Key
|
||||||
floCrypto.verifyPrivKey(privateKey, pubKey_floID, *isfloID)
|
floCrypto.verifyPrivKey(privateKey, pubKey_floID, *isfloID)
|
||||||
`verifyPrivKey` verify the private-key for the given public-key or flo-ID
|
`verifyPrivKey` verify the private-key for the given public-key or flo-ID
|
||||||
@ -223,27 +120,12 @@ In addition, we have these system variables outside FLO Globals but used globall
|
|||||||
3. isfloID - boolean value (true: compare as flo ID, false: compare as public key) (optional, default is true)
|
3. isfloID - boolean value (true: compare as flo ID, false: compare as public key) (optional, default is true)
|
||||||
* Returns : boolen (true or false)
|
* Returns : boolen (true or false)
|
||||||
|
|
||||||
#### Validate Address
|
|
||||||
floCrypto.validateAddr(address, *std, *bech)
|
|
||||||
`validateAddr` check if the given Address (any blockchain) is valid or not
|
|
||||||
1. address - address to validate
|
|
||||||
2. std - checks for legacy version (optional, default=true) (true: allow any, array: list of versions, value: one version only, false: allow none)
|
|
||||||
3. bech - checks for bech version (optional, default=true) (true: allow any, array: list of versions, value: one version only, false: allow none)
|
|
||||||
* Returns : boolen (true or false)
|
|
||||||
|
|
||||||
#### Validate FLO ID
|
#### Validate FLO ID
|
||||||
floCrypto.validateFloID(floID)
|
floCrypto.validateAddr(floID)
|
||||||
`validateFloID` check if the given floID is valid or not
|
`validateAddr` check if the given Address is valid or not
|
||||||
1. floID - flo ID to validate
|
1. floID - flo ID to validate
|
||||||
* Returns : boolen (true or false)
|
* Returns : boolen (true or false)
|
||||||
|
|
||||||
#### Verify Public Key
|
|
||||||
floCrypto.verifyPubKey(publicKey, address)
|
|
||||||
`verifyPubKey` verify the public key for the given address (any blockchain)
|
|
||||||
1. publicKey - public key
|
|
||||||
2. address - address to verify
|
|
||||||
* Returns : boolen (true or false)
|
|
||||||
|
|
||||||
#### Data Encryption
|
#### Data Encryption
|
||||||
floCrypto.encryptData(data, publicKey)
|
floCrypto.encryptData(data, publicKey)
|
||||||
`encryptData` encrypts the given data using public-key
|
`encryptData` encrypts the given data using public-key
|
||||||
@ -311,8 +193,6 @@ In addition, we have these system variables outside FLO Globals but used globall
|
|||||||
## FLO Blockchain API Operations
|
## 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.
|
`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.
|
||||||
|
|
||||||
#### Important: FLO Blockchain API operations have all been promisified. All output needs to be handled using .then These operations do not return function return values. Once again, they resolve do not return.
|
|
||||||
|
|
||||||
#### promisedAJAX
|
#### promisedAJAX
|
||||||
floBlockchainAPI.promisedAJAX(method, uri)
|
floBlockchainAPI.promisedAJAX(method, uri)
|
||||||
`promisedAJAX` requests data using API
|
`promisedAJAX` requests data using API
|
||||||
@ -392,42 +272,37 @@ Note: If passed as Array, then ratio of the balance of the senders are preserved
|
|||||||
`readData` reads FLO data from transactions of specified address
|
`readData` reads FLO data from transactions of specified address
|
||||||
1. addr - FLO address for which the transactions data has to be read.
|
1. addr - FLO address for which the transactions data has to be read.
|
||||||
2. options - Contains options for filter data from transactions.
|
2. options - Contains options for filter data from transactions.
|
||||||
- limit : maximum number of filtered data (default = no limit)
|
- limit : maximum number of filtered data (default = 1000, negative = no limit)
|
||||||
- ignoreOld : ignore old transactions (default = 0)
|
- ignoreOld : ignore old transactions (default = 0)
|
||||||
- sentOnly : filters only sent data
|
- sentOnly : filters only sent data
|
||||||
- pattern : filters data that contains pattern as an object key in the JSON string
|
- 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] == '$'})
|
- filter : custom filter funtion for floData (eg . filter: d => {return d[0] == '$'})
|
||||||
* Resolves: Object {totalTxs, floData (Array)}
|
* Resolves: Object {totalTxs, floData (Array)}
|
||||||
|
|
||||||
## Compact IndexedDB operations
|
## Compact IndexedDB operations
|
||||||
`compactIDB` operations can be used to perform basic IndexedDB operations such as add, read/write, modify and remove. These functions are asynchronous and return a promise. Contains the following operations.
|
`compactIDB` operations can be used to perform basic IndexedDB operations such as add, read/write, modify and remove.Contains following operations.
|
||||||
|
|
||||||
#### Important: Compact IndexedDB operations have all been promisified. All output needs to be handled using .then These operations do not return function return values. Once again, they resolve: they do not return.
|
|
||||||
|
|
||||||
#### setDefaultDB
|
#### setDefaultDB
|
||||||
compactIDB.setDefaultDB(dbName)
|
compactIDB.setDefaultDB(dbName)
|
||||||
`setDefaultDB` sets the database on which further operations will be performed.
|
`setDefaultDB` sets the database on which further operations will be performed.
|
||||||
1. dbName - This is the name of default database to be used.
|
1. dbName - This is the name of default database to be used.
|
||||||
* Note: this operation is neither promisified nor returns a value. It just sets the default DB
|
|
||||||
|
|
||||||
#### initDB
|
#### initDB
|
||||||
compactIDB.initDB(dbName, objectStores = {})
|
compactIDB.initDB(dbName, objectStores = {})
|
||||||
`initDB` initializes new IndexedDB.
|
`initDB` initializes new IndexedDB.
|
||||||
1. dbName - Specifies database to be initialized.
|
1. dbName - Specifies database to be initialized.
|
||||||
2. objectStores - This is an object containing various objectStores to be initiazed when creating an IDB.
|
2. objectStores - This is an object containing various objectStores to be initiazed when creating an IDB.
|
||||||
* Resolves: Status (string) | Rejects: error
|
|
||||||
|
|
||||||
#### openDB
|
#### openDB
|
||||||
compactIDB.openDB(dbName = this.defaultDB)
|
compactIDB.openDB(dbName = this.defaultDB)
|
||||||
`openDB` returns a promise that resolves to a default database object.
|
`openDB` returns a promise that resolves to a default database object.
|
||||||
1. dbName - Name of the database (optional, uses defaultDB if not specified)
|
1. dbName - Name of the database (optional, uses defaultDB if not specified)
|
||||||
* Resolves: database (IDB) | Rejects: error
|
|
||||||
|
|
||||||
#### deleteDB
|
#### deleteDB
|
||||||
compactIDB.deleteDB(dbName = this.defaultDB)
|
compactIDB.deleteDB(dbName = this.defaultDB)
|
||||||
`deleteDB` deletes the specified database.
|
`deleteDB` deletes the specified database.
|
||||||
1. dbName - Name of the database (optional, uses defaultDB if not specified)
|
1. dbName - Name of the database (optional, uses defaultDB if not specified)
|
||||||
* Resolves: Status (string) | Rejects: error
|
|
||||||
|
|
||||||
#### writeData
|
#### writeData
|
||||||
compactIDB.writeData(obsName, data, key = false, dbName = this.defaultDB)
|
compactIDB.writeData(obsName, data, key = false, dbName = this.defaultDB)
|
||||||
@ -436,7 +311,6 @@ Note: If passed as Array, then ratio of the balance of the senders are preserved
|
|||||||
2. data - data that has to be written in specified object store.
|
2. data - data that has to be written in specified object store.
|
||||||
3. key - Primary key of the data (optional, false indicates key is autoincremented or passed in data)
|
3. key - Primary key of the data (optional, false indicates key is autoincremented or passed in data)
|
||||||
4. dbName - Name of the database (optional, uses defaultDB if not specified)
|
4. dbName - Name of the database (optional, uses defaultDB if not specified)
|
||||||
* Resolves: Status (string) | Rejects: error
|
|
||||||
|
|
||||||
#### addData
|
#### addData
|
||||||
compactIDB.addData(obsName, data, key = false, dbName = this.defaultDB)
|
compactIDB.addData(obsName, data, key = false, dbName = this.defaultDB)
|
||||||
@ -445,7 +319,6 @@ Note: If passed as Array, then ratio of the balance of the senders are preserved
|
|||||||
2. data - The data which has to be added to obeject store.
|
2. data - The data which has to be added to obeject store.
|
||||||
3. key - Primary key of the data (optional, false indicates key is autoincremented or passed in data)
|
3. key - Primary key of the data (optional, false indicates key is autoincremented or passed in data)
|
||||||
4. dbName - Name of the database (optional, uses defaultDB if not specified)
|
4. dbName - Name of the database (optional, uses defaultDB if not specified)
|
||||||
* Resolves: Status (string) | Rejects: error
|
|
||||||
|
|
||||||
#### removeData
|
#### removeData
|
||||||
compactDB.removeData(obsName, key, dbName = this.defaultDB)
|
compactDB.removeData(obsName, key, dbName = this.defaultDB)
|
||||||
@ -453,14 +326,12 @@ Note: If passed as Array, then ratio of the balance of the senders are preserved
|
|||||||
1. obsName - Name of object store from which the data has to be removed.
|
1. obsName - Name of object store from which the data has to be removed.
|
||||||
2. key - Primary key of the data.
|
2. key - Primary key of the data.
|
||||||
3. dbName - Name of the database (optional, uses defaultDB if not specified)
|
3. dbName - Name of the database (optional, uses defaultDB if not specified)
|
||||||
* Resolves: Status (string) | Rejects: error
|
|
||||||
|
|
||||||
#### clearData
|
#### clearData
|
||||||
compactDB.clearData(obsName, dbName = this.defaultDB)
|
compactDB.clearData(obsName, dbName = this.defaultDB)
|
||||||
`clearData` clears all data in the objectStore.
|
`clearData` clears all data in the objectStore.
|
||||||
1. obsName - Name of object store from which the data has to be removed.
|
1. obsName - Name of object store from which the data has to be removed.
|
||||||
2. dbName - Name of the database (optional, uses defaultDB if not specified)
|
2. dbName - Name of the database (optional, uses defaultDB if not specified)
|
||||||
* Resolves: Status (string) | Rejects: error
|
|
||||||
|
|
||||||
#### readData
|
#### readData
|
||||||
compactDB.readData(obsName, key, dbName = this.defaultDB)
|
compactDB.readData(obsName, key, dbName = this.defaultDB)
|
||||||
@ -468,55 +339,18 @@ Note: If passed as Array, then ratio of the balance of the senders are preserved
|
|||||||
1. obsName - Name of object store from which the data has to be retrieved.
|
1. obsName - Name of object store from which the data has to be retrieved.
|
||||||
2. key - Primary key of the data to read.
|
2. key - Primary key of the data to read.
|
||||||
3. dbName - Name of the database (optional, uses defaultDB if not specified)
|
3. dbName - Name of the database (optional, uses defaultDB if not specified)
|
||||||
* Resolves: data (Object) | Rejects: error
|
|
||||||
|
|
||||||
#### readAllData
|
#### readAllData
|
||||||
compactDB.readAllData(obsName, dbName = this.defaultDB)
|
compactDB.readAllData(obsName, dbName = this.defaultDB)
|
||||||
`readAllData` reads all the data from specified object store using IndexedDB openCursor method.
|
`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.
|
1. obsName - Name of object store from which the data has to be retrieved.
|
||||||
2. dbName - Name of the database (optional, uses defaultDB if not specified)
|
2. dbName - Name of the database (optional, uses defaultDB if not specified)
|
||||||
* Resolves: data (Object) | Rejects: error
|
|
||||||
|
## FLO Supernode module
|
||||||
|
This module contains functions that interact with the supernode to send and retrive data in the backend. Use floClouldAPI operations to send and receive data for application.
|
||||||
|
|
||||||
## FLO Cloud API operations
|
## FLO Cloud API operations
|
||||||
`floCloudAPI` operations can interact with floSupernode cloud to send and retrieve data for applications. floCloudAPI uses floSupernode module for backend interactions. FLO Cloud API functions are promisified and resolves the data or status.
|
`floCloudAPI` operations can interact with floSupernode cloud to send and retrieve data for applications. floCloudAPI uses floSupernode module for backend interactions.
|
||||||
|
|
||||||
#### NEW providing callback as a new option for automatic refresh of data in browser
|
|
||||||
We have implemented a new feature where Standard Operations page can listen to any new general data or new object data and request for new data will be automatic. As soon as a new data is available in a cloud address, it will be notified to listening clients. This has been achieved by expanding `options` in `requestGeneralData` and `requestObjectData`
|
|
||||||
|
|
||||||
A blank callback function can be provided as an option in `requestGeneralData` in which case the `floGlobals.generalData` will be automatically updated with new generalData relevant to that `requestGeneralData` whenever such data is available with the cloud.
|
|
||||||
|
|
||||||
A blank callback function when added as an option to `requestObjectData` will automatically update `floGlobals.appObjects` with all refreshes ar available to that `requestObjectData` without calling `requestObjectData` again explicitly.
|
|
||||||
|
|
||||||
You can also specify an actual function name in callback, in which case that function will also be executed in addition to automatic data refreshes. This is usually for UI refresh needs. Alternately, one can monitor changes to `floGlobals.generalData` and `floGlobals.appObjects` via an event mechanism, and update UI whenever there are changes, in which case blank callback function is sufficient.
|
|
||||||
|
|
||||||
Note: Callbacks have been implemented using persistent listening websockets in both browsers and the cloud. So it will load the cloud with persistent memory requirements. Use it only when it is absolutely needed.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
|
|
||||||
//Requesting general data of type1 with calls in two equivalent ways using callback option for automatic refresh
|
|
||||||
floCloudAPI.requestGeneralData("type1", {callback(){}})
|
|
||||||
floCloudAPI.requestGeneralData("type1", {callback: _=> null})
|
|
||||||
|
|
||||||
//Requesting Object data for article_valuation_individual object using callback option for automatic refresh
|
|
||||||
floCloudAPI.requestObjectData("article_valuation_individual", {callback: _=> null) })
|
|
||||||
|
|
||||||
//They are all equivalent
|
|
||||||
floCloudAPI.requestGeneralData("type1", {senderID:[x,y,z], callback: ()=>{} })
|
|
||||||
floCloudAPI.requestGeneralData("type1", {senderID:[x,y,z], callback(){} })
|
|
||||||
floCloudAPI.requestGeneralData("type1", {senderID:[x,y,z], callback: _=> null })
|
|
||||||
|
|
||||||
//Implementation of a callback function that will update the UI and display data in console
|
|
||||||
floCloudAPI.requestObjectData("article_valuation_individual", {callback: fnToUseTheData} )
|
|
||||||
|
|
||||||
fnToUseTheData example:
|
|
||||||
fnToUseTheData: function (data, error) {
|
|
||||||
if(!error) {
|
|
||||||
console.log(data);
|
|
||||||
//Add Update UI function
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### sendApplicationData
|
#### sendApplicationData
|
||||||
floCloudAPI.sendApplicationData(message, type, options = {})
|
floCloudAPI.sendApplicationData(message, type, options = {})
|
||||||
@ -524,13 +358,11 @@ if(!error) {
|
|||||||
1. message - data to be sent
|
1. message - data to be sent
|
||||||
2. type - type of the data
|
2. type - type of the data
|
||||||
3. options - (optional, options detailed at end of module)
|
3. options - (optional, options detailed at end of module)
|
||||||
* Resolves: Sent-Status (string) | Rejects: error
|
|
||||||
|
|
||||||
#### requestApplicationData
|
#### requestApplicationData
|
||||||
floCloudAPI.requestApplicationData(options = {})
|
floCloudAPI.requestApplicationData(options = {})
|
||||||
`requestApplicationData` requests application data from the cloud.
|
`requestApplicationData` requests application data from the cloud.
|
||||||
1. options - (optional, options detailed at end of module)
|
1. options - (optional, options detailed at end of module)
|
||||||
* Resolves: data (Object) | Rejects: error
|
|
||||||
|
|
||||||
#### sendGeneralData
|
#### sendGeneralData
|
||||||
floCloudAPI.sendGeneralData(message, type, options = {})
|
floCloudAPI.sendGeneralData(message, type, options = {})
|
||||||
@ -538,256 +370,149 @@ if(!error) {
|
|||||||
1. message - data to be sent
|
1. message - data to be sent
|
||||||
2. type - type of the data
|
2. type - type of the data
|
||||||
3. options - (optional, options detailed at end of module)
|
3. options - (optional, options detailed at end of module)
|
||||||
* Resolves: Sent-Status (string) | Rejects: error
|
|
||||||
|
|
||||||
###### Minimal Example:
|
|
||||||
floCloudAPI.sendGeneralData("Hello World", "type1")
|
|
||||||
Sends "Hello World" message to the cloud as General Data with type1 as `type` with `myFloID` as default sender and `floGlobals.adminID` as receiver
|
|
||||||
|
|
||||||
#### requestGeneralData
|
#### requestGeneralData
|
||||||
floCloudAPI.requestGeneralData(type, options = {})
|
floCloudAPI.requestGeneralData(type, options = {})
|
||||||
`requestGeneralData` requests application data from the cloud.
|
`requestGeneralData` requests application data from the cloud.
|
||||||
1. type - type of the data
|
1. type - type of the data
|
||||||
2. options - (optional, options detailed at end of module)
|
2. options - (optional, options detailed at end of module)
|
||||||
* Resolves: Status (string) | Rejects: error
|
|
||||||
|
|
||||||
###### Minimal Example:
|
|
||||||
```
|
|
||||||
floCloudAPI.requestGeneralData("type1")
|
|
||||||
Requests all messages of General Data nature from the cloud with type1 as `type` sent by anyone and `floGlobals.adminID` as default receiverID
|
|
||||||
```
|
|
||||||
#### resetObjectData
|
#### resetObjectData
|
||||||
floCloudAPI.resetObjectData("objectName", options = {})
|
floCloudAPI.resetObjectData(objectName, options = {})
|
||||||
`resetObjectData` resets the objectData to cloud.
|
`resetObjectData` resets the objectData to cloud.
|
||||||
1. "objectName" - Name of the objectData to be reset. Quotes are must
|
1. objectName - Name of the objectData to be reset
|
||||||
2. options - (optional, options detailed at end of module)
|
2. options - (optional, options detailed at end of module)
|
||||||
* Resolves: Sent-Status (string) | Rejects: error
|
Note: value of objectData is taken from floGlobals
|
||||||
|
|
||||||
Note: value of objectData is taken from floGlobals.appObjects["objectName"]
|
|
||||||
The object data corresponding with Object Name must be defined in floGlobals.appObjects["objectName"] before a reset can be done
|
|
||||||
|
|
||||||
###### Minimal Example:
|
|
||||||
floGlobals.appObjects["myFirstObject"] = {a:1,b:2}
|
|
||||||
floCloudAPI.resetObjectData("myFirstObject")
|
|
||||||
Initiates "myFirstObject" with {a:1,b:2}, and sends to cloud with `myFloID` as default sender and `floGlobals.adminID` as receiver
|
|
||||||
|
|
||||||
#### updateObjectData
|
#### updateObjectData
|
||||||
floCloudAPI.updateObjectData("objectName", options = {})
|
floCloudAPI.updateObjectData(objectName, options = {})
|
||||||
`updateObjectData` updates the objectData to cloud.
|
`updateObjectData` updates the objectData to cloud.
|
||||||
1. "objectName" - Name of the objectData to be updated. Quotes are must.
|
1. objectName - Name of the objectData to be updated
|
||||||
2. options - (optional, options detailed at end of module)
|
2. options - (optional, options detailed at end of module)
|
||||||
* Resolves: Sent-Status (string) | Rejects: error
|
Note: value of objectData is taken from floGlobals
|
||||||
|
|
||||||
Note: value of objectData is taken from floGlobals.appObjects["objectName"]
|
|
||||||
The object data corresponding with Object Name must be defined in floGlobals.appObjects["objectName"] before an update can be done
|
|
||||||
|
|
||||||
###### Minimal Example:
|
|
||||||
floGlobals.appObjects["myFirstObject"] = {a:1,c:3,d:4}
|
|
||||||
floCloudAPI.updateObjectData("myFirstObject")
|
|
||||||
Updates "myFirstObject" with {a:1,c:3,d:4}, and sends to cloud with `myFloID` as default sender and `floGlobals.adminID` as receiver.
|
|
||||||
|
|
||||||
#### requestObjectData
|
#### requestObjectData
|
||||||
floCloudAPI.requestObjectData("objectName", options = {})
|
floCloudAPI.requestObjectData(objectName, options = {})
|
||||||
`requestObjectData` requests application data from the cloud.
|
`requestObjectData` requests application data from the cloud.
|
||||||
1. "objectName" - Name of the objectData to be requested. Quotes are must.
|
1. objectName - Name of the objectData to be requested
|
||||||
2. options - (optional, options detailed at end of module)
|
2. options - (optional, options detailed at end of module)
|
||||||
* Resolves: Status (string) | Rejects: error
|
|
||||||
|
|
||||||
Note: The output is available at floGlobals.appObjects["objectName"] after the promise is resolved
|
#### options:
|
||||||
|
* send options:
|
||||||
|
* receiverID - received of the data
|
||||||
|
* application - application of the data
|
||||||
|
* comment - comment of the data
|
||||||
|
|
||||||
###### Minimal Example:
|
* request options
|
||||||
floCloudAPI.requestObjectData("myFirstObject")
|
* receiverID - received of the data
|
||||||
Requests the latest value of "myFirstObject" from the cloud. The request is sent to cloud with `myFloID` as sender and `floGlobals.adminID` as receiver. The output is available at floGlobals.appObjects["myFirstObject"]
|
* senderIDs - array of senderIDs
|
||||||
|
* application - application of the data
|
||||||
|
* type - type of the data
|
||||||
|
* comment - comment of the data
|
||||||
|
* lowerVectorClock - VC from which the data is to be requested
|
||||||
|
* upperVectorClock - VC till which the data is to be requested
|
||||||
|
* atVectorClock - VC at which the data is to requested
|
||||||
|
* mostRecent - boolean (true: request only the recent data matching the pattern)
|
||||||
|
|
||||||
#### sendApplicationData
|
## FLO Decentralised Applications (Dapps)
|
||||||
floCloudAPI.sendApplicationData(message, type, options = {})
|
`floDapps` module contains methods for basic Dapp. floDapps uses all of the above modules.
|
||||||
`sendApplicationData` sends application data to the cloud.
|
|
||||||
1. message - data to be sent
|
|
||||||
2. type - type of the data
|
|
||||||
3. options - (optional, options detailed at end of module)
|
|
||||||
|
|
||||||
Note: type is mandatory while sending but optional while requesting in case of Application Data. This allows ApplicationData to span different types in retrieval.
|
|
||||||
|
|
||||||
###### Minimal Example:
|
|
||||||
floCloudAPI.sendApplicationData("Hello Application World", "typeA")
|
|
||||||
Sends "Hello Application World" message to the cloud as Application Data with typeA as `type` with `myFloID` as default sender and `floGlobals.adminID` as receiver
|
|
||||||
|
|
||||||
#### requestApplicationData
|
#### setCustomPrivKeyInput
|
||||||
floCloudAPI.requestApplicationData(options = {})
|
floDapps.setCustomPrivKeyInput(customFn)
|
||||||
`requestApplicationData` requests application data from the cloud.
|
`setCustomPrivKeyInput` adds a startup funtion to the Dapp
|
||||||
1. options - (optional, options detailed at end of module)
|
1. customFn - custom function to get login credentials (privateKey)
|
||||||
|
|
||||||
Note: type is mandatory while sending but optional while requesting in case of Application Data. This allows ApplicationData to span different types in retrieval.
|
|
||||||
|
|
||||||
Note: Application Data results are not stored in local IndexedDB by Standard Operations Framework.
|
#### manageSubAdmins
|
||||||
|
floDapps.manageSubAdmins(adminPrivKey, addList, rmList)
|
||||||
|
`manageSubAdmins` adds and/or removes subAdmins by sending transaction to blockchain.
|
||||||
|
1. adminPrivKey - private key of the adminID
|
||||||
|
2. addList - Array of subAdmins to add
|
||||||
|
3. rmList - Array of subAdmins to remove
|
||||||
|
|
||||||
Note: If a blank REQUEST APPLICATION DATA is made, then cloud will give all application data at the admin ID of the application
|
#### clearCredentials
|
||||||
|
floDapps.clearCredentials()
|
||||||
|
`clearCredentials` removes the login credentials stored in IDB.
|
||||||
|
|
||||||
###### Minimal Example:
|
#### securePrivKey
|
||||||
```
|
floDapps.securePrivKey(pwd)
|
||||||
floCloudAPI.requestApplicationData()
|
`securePrivKey` replaces the stored private key with an encrypted variant
|
||||||
Requests all messages of Apllication Data nature from the cloud with any `type` sent by anyone and `floGlobals.adminID` as receiverID
|
1. pwd - password for the encryption
|
||||||
```
|
Note: if securePrivKey is used, then password must be requested during customPrivKeyInput (in setCustomPrivKeyInput).
|
||||||
|
|
||||||
## 4. GENERAL DATA PARAMETERS AND OPTIONS
|
#### getNextGeneralData
|
||||||
|
floDapps.getNextGeneralData(type, vectorClock, options = {})
|
||||||
|
`getNextGeneralData` return the next generaldata
|
||||||
|
1. type - type of the general data
|
||||||
|
2. vectorClock - current known VC, from which next data to be retrived
|
||||||
|
3. options - (optional, {comment, application} of the data)
|
||||||
|
|
||||||
### SEND GENERAL DATA PARAMETERS AND OPTIONS
|
#### launchStartUp
|
||||||
Parameters while sending
|
floDapps.launchStartUp()
|
||||||
|
`launchStartUp` launchs the Dapp startup functions
|
||||||
|
|
||||||
* `Message`: Actual Message to be sent
|
##### reactorEvents
|
||||||
* `Type`: User defined type (anything that user wants to classify as type)
|
* startUpSuccessLog - called when a startup funtion is successfully completed
|
||||||
|
* startUpErrorLog - called when a startup funtion encounters an error
|
||||||
|
|
||||||
#### Options
|
##### onLoadStartUp
|
||||||
* `receiverID` - receiver of the data
|
Sample startup is defined in onLoadStartUp function
|
||||||
* `application` - name of the application sending the general data
|
|
||||||
* `comment` - user comment for the data
|
|
||||||
|
|
||||||
Important: Never use senderIDs in SEND GENERAL DATA options. The system automatically picks the FLO ID of the sender from FLO Globals. Its a common mistake developers make.
|
#### addStartUpFunction
|
||||||
|
floDapps.addStartUpFunction(fname, fn)
|
||||||
|
`addStartUpFunction` adds a startup funtion to the Dapp
|
||||||
|
1. fname - Name of the startup function
|
||||||
|
2. fn - body of the function
|
||||||
|
Note: startup funtions are called in parallel. Therefore only add custom startup funtion only if it can run in parallel with other startup functions. (default startup funtions are read supernode list and subAdmin list from blockchain API, load data from indexedDB, get login credentials)
|
||||||
|
|
||||||
Type is mandatory in SEND GENERAL DATA because without at least one data identifier like TYPE, the message cannot be retrieved back
|
### Advanced Dapp functions usually not needed for users
|
||||||
|
|
||||||
Application field is used by the Cloud to judge whether this message should be deleted after 7 days, or stored permanently. Developers should be careful not to change value of application field if the default value enables the message to be stored permanently. Currently messages sent from subadmins to any receiverID for applications notified by the cloud in the FLO Blockchain are stored permanently. So do not change application field as a caution. Use the comment field.
|
#### setAppObjectStores
|
||||||
|
floDapps.setAppObjectStores(appObs)
|
||||||
|
`setAppObjectStores` adds additionals objectstores for the app
|
||||||
|
1. appObs - additionals objects for the app
|
||||||
|
|
||||||
###### Minimal Example:
|
#### objectDataMapper
|
||||||
```
|
floDapps.objectDataMapper(object, path, data)
|
||||||
floCloudAPI.sendGeneralData("Hello World", "type1")
|
`objectDataMapper` maps the object and data via path
|
||||||
Sends "Hello World" message to the cloud as General Data with type1 as `type` with `myFloID` as default sender and `floGlobals.adminID` as receiver
|
1. object - object to be mapped
|
||||||
```
|
2. path - end path for the data holder
|
||||||
### REQUEST GENERAL DATA PARAMETERS AND OPTIONS
|
3. data - data to be pushed in map
|
||||||
|
|
||||||
Parameters while requesting
|
|
||||||
|
|
||||||
* `Type`: User defined type (retrieves all data of that type which the sender might have used in SEND DATA phase)
|
# Repeat of Basic Concepts of RanchiMall Blockchain Cloud for developers
|
||||||
|
|
||||||
#### request options
|
* RanchiMall blockchain cloud is a service to provide a set of decentralized servers that will provide data storage to users. These decentralized servers are listed in the FLO Blockchain under an authorized FLO address. This gives the assurance to the users that those data servers can be trusted.
|
||||||
* `receiverID` - receiver FLO ID of the data. ReceiverID is always a single value in our cloud design
|
|
||||||
* `senderIDs` - array of senderIDs. This must be in an array even if a single senderID is requested
|
|
||||||
* `application` - name of the application that sent the general data
|
|
||||||
* `comment` - comments for the data
|
|
||||||
* `lowerVectorClock` - VC from which the data is to be requested
|
|
||||||
* `upperVectorClock` - VC till which the data is to be requested
|
|
||||||
* `atVectorClock` - VC at which the data is to requested
|
|
||||||
* `mostRecent` - boolean (true: request only the recent data matching the pattern. Just the last one)
|
|
||||||
* `callback(){}` - will initialize websocket for automatic updates.
|
|
||||||
* `callback: fnToUseTheData` - will initialize websocket for automatic updates, and will alse execute fnToUseTheData
|
|
||||||
|
|
||||||
The results are available in floGlobals.generalData after the request promise has been resolved
|
* The user can store his data in these servers freely. RanchiMall cloud service also provides facilities to store Javascript Objects directly. Storage in JavaScript object form makes it easier for JavaScript based applications to process the data.
|
||||||
|
|
||||||
floDapps.getNextGeneralData should be used to retrieve the output in the client browser where the retrieval from floGlobals.generalData has been simplified.
|
* The cloud servers provide automatic backup and restoration for each other.
|
||||||
|
|
||||||
Type is mandatory in REQUEST GENERAL DATA. So every TYPE of general data must be requested individually and separately.
|
* Using the blockchain based data cloud, users will not need any database to store their data. The cloud will provide data storage, backup and restoration facilites.
|
||||||
|
|
||||||
If you want to use requests to give results from all types at one go, use Application Data.
|
* RanchiMall Blockchain Cloud is a password less system. Every sender has to digitally sign his data with the private key associated with its FLO ID. The cloud verifies the digital signature of the sender before storing sender data.
|
||||||
|
|
||||||
Application field is used by the Cloud to judge whether this message should be deleted after 7 days, or stored permanently. Developers should be careful not to change value of application field if the default value enables the message to be stored permanently. Currently messages sent from subadmins to any receiverID for applications notified by the cloud in the FLO Blockchain are stored permanently. So do not change application field as a caution. Use the comment field.
|
* Since the blockchain cloud is an ensemble of servers, we need a method to pick the right server to store the data. For this purpose, we find the a server closest to receipient of the data according to an artificial distance measure.
|
||||||
|
|
||||||
###### Minimal Example:
|
* Every client of the cloud can automatically compute the correct server where the data needs to be stored, and sends the message directly to that server.
|
||||||
```
|
|
||||||
floCloudAPI.requestGeneralData("type1")
|
|
||||||
Requests all messages of General Data nature from the cloud with type1 as `type` sent by anyone and `floGlobals.adminID` as default receiverID
|
|
||||||
|
|
||||||
floCloudAPI.requestGeneralData("type1", {senderID:[x,y,z], callback: ()=>{}})
|
* Every client of the cloud is the consumer of the data. It can ask the cloud for data sorted by a recipient, or by various options we provide like name of application, type of data, or by specific comments. The client can can also ask for all data after or before a certain point of time using a concept called Vector Clock.
|
||||||
floCloudAPI.requestGeneralData("type1", {senderID:[x,y,z], callback(){}})
|
|
||||||
floCloudAPI.requestGeneralData("type1", {senderID:[x,y,z], callback: _=> null})
|
|
||||||
|
|
||||||
floCloudAPI.requestObjectData("article_valuation_individual", {callback: fnToUseTheData})
|
* The cloud attaches the exact epoch time to any message given by a sender, and using the combination of epoch time, and sender FLO ID, the vector clock is constructed.
|
||||||
|
|
||||||
fnToUseTheData example:
|
* The two basic forms in which users can submit data to the cloud are `General Data` and `Object Data`. `General Data` is freely flowing data, and `Object Data` is stored directly as pure Javascript Object.
|
||||||
fnToUseTheData: function (data, error) {
|
|
||||||
if(!error) console.log(data)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. OBJECT DATA PARAMETERS AND OPTIONS
|
* Both `General Data` and `Object Data` have been derived from `Application Data` which is the basic system data type in the cloud. Normal users will never need to use Application Data. But for documentation purposes, we are providing the technical details for Application Data as well.
|
||||||
|
|
||||||
### RESET or UPDATE OBJECT DATA PARAMETERS AND OPTIONS
|
- `General Data` = `Application Data` + User level Vector Clock filtering facilities
|
||||||
Parameters while resetting or updating
|
|
||||||
* `Object Name`: Name of the object with data populated in floGlobals.appObjects[objectName]
|
|
||||||
|
|
||||||
Note: The object data corresponding with Object Name must be defined in floGlobals.appObjects[objectName] before a reset or update can be done
|
- `Object Data` = `Application Data` + Message field modified to handle Javascript Object + User level Vector Clock filtering facilities
|
||||||
|
|
||||||
#### Options
|
* Consistent with blockchain data principles, RanchiMall blockchain cloud will also provide data to everyone who asks for it. So sensitive data should be encrypted using the receiver's public key using Crypto functions of FLO Standard Operations.
|
||||||
* `receiverID` - receiver FLO ID of the data. If this is not specified, the admin ID will be taken as receiverID
|
|
||||||
* `application` - name of the application for sending the object data
|
|
||||||
* `comment` - comment for the object data
|
|
||||||
|
|
||||||
Note: Never use senderIDs in RESET and UPDATE. The system automatically picks the FLO ID of the sender from FLO Globals. Its a common mistake developers make.
|
* Consumers of data can ask for data by receiver ID, or filter it by application, type, or comment. They can also ask for data for a given type before and after a certain vector clock.
|
||||||
|
|
||||||
Note: Type field is never used in RESET, UPDATE or REQUEST operations in Object Data. Type field is internally blocked for Object Data.
|
|
||||||
|
|
||||||
### REQUEST OBJECT DATA PARAMETERS AND OPTIONS
|
|
||||||
|
|
||||||
#### Mandatory
|
|
||||||
`Object Name`
|
|
||||||
|
|
||||||
#### request options
|
|
||||||
* `receiverID` - receiver FLO ID of the data. ReceiverID is always a single value in our cloud design. If this is not specified, the admin ID will be taken as receiverID
|
|
||||||
* `senderIDs` - array of senderIDs. This must be in an array even if a single senderID is requested
|
|
||||||
* `application` - name of the application for which the object data is intended
|
|
||||||
* `comment` - comment for the object data
|
|
||||||
* `lowerVectorClock` - VC from which the data is to be requested
|
|
||||||
* `upperVectorClock` - VC till which the data is to be requested
|
|
||||||
* `atVectorClock` - VC at which the data is to requested
|
|
||||||
* `mostRecent` - boolean (true: request only the recent data matching the pattern. Just the last one.)
|
|
||||||
* `callback(){}` - will initialize websocket for automatic updates.
|
|
||||||
* `callback: fnToUseTheData` - will initialize websocket for automatic updates, and will alse execute fnToUseTheData
|
|
||||||
|
|
||||||
The output is available in floGlobals.appObjects[objectName] after request Object Data promise is resolved
|
|
||||||
|
|
||||||
Type field is never used while RESET, UPDATE or REQUEST operations in Object Data. Type field is internally blocked for Object Data.
|
|
||||||
|
|
||||||
Application field is used by the Cloud to judge whether this message should be deleted after 7 days, or stored permanently. Developers should be careful not to change value of application field if the default value enables the message to be stored permanently. Currently messages sent from subadmins to any receiverID for applications notified by the cloud in the FLO Blockchain are stored permanently. So do not change application field as a caution. Use the comment field.
|
|
||||||
|
|
||||||
## 6. APPLICATION DATA PARAMETERS, OPTIONS AND EXPLANATIONS
|
|
||||||
|
|
||||||
### SEND APPLICATION DATA PARAMETERS AND OPTIONS
|
|
||||||
Parameters while sending
|
|
||||||
|
|
||||||
* `Message`: Actual Message to be sent
|
|
||||||
* `Type`: User defined type (anything that user wants to classify as type)
|
|
||||||
|
|
||||||
#### Options
|
|
||||||
* `receiverID` - receiver of the data. If this is not specified, the admin ID will be taken as receiverID
|
|
||||||
* `application` - name of the application sending the data
|
|
||||||
* `comment` - user comment for the data
|
|
||||||
|
|
||||||
Important: Never use senderIDs in SEND DATA options. The system automatically picks the FLO ID of the sender from FLO Globals. Its a common mistake developers make.
|
|
||||||
|
|
||||||
Type is mandatory in SEND APPLICATION DATA because without at least one data identifier like TYPE, the message cannot be retrieved back
|
|
||||||
|
|
||||||
Application field is used by the Cloud to judge whether this message should be deleted after 7 days, or stored permanently. Developers should be careful not to change value of application field if the default value enables the message to be stored permanently. Currently messages sent from subadmins to any receiverID for applications notified by the cloud in the FLO Blockchain are stored permanently. So do not change application field as a caution. Use the comment field.
|
|
||||||
|
|
||||||
### REQUEST APPLICATION DATA PARAMETERS AND OPTIONS
|
|
||||||
|
|
||||||
#### Mandatory Parameters while requesting
|
|
||||||
None
|
|
||||||
|
|
||||||
#### request options
|
|
||||||
* `receiverID` - receiver FLO ID of the data. If this is not specified, the admin ID will be taken as receiverID. It is always a single value.
|
|
||||||
* `Type`: User defined type (retrieves all data of that type which the sender might have used in SEND DATA phase)
|
|
||||||
* `senderIDs` - array of senderIDs. This must be in an array even if a single senderID is requested.
|
|
||||||
* `application` - application of the data
|
|
||||||
* `comment` - comment of the data
|
|
||||||
* `lowerVectorClock` - VC from which the data is to be requested
|
|
||||||
* `upperVectorClock` - VC till which the data is to be requested
|
|
||||||
* `atVectorClock` - VC at which the data is to requested
|
|
||||||
* `mostRecent` - boolean (true: request only the recent data matching the pattern. Just the last one)
|
|
||||||
* `callback(){}` - will initialize websocket for automatic updates.
|
|
||||||
* `callback: fnToUseTheData` - will initialize websocket for automatic updates, and will alse execute fnToUseTheData
|
|
||||||
|
|
||||||
Note: Results will be available in promise.resolve(requestApplicationData(..)), and user will have to handle the response himself.
|
|
||||||
|
|
||||||
Note: TYPE is mandatory while SEND APPLICATION DATA but not in REQUEST APPLICATION DATA. This enables a single Application Data request to fetch across all TYPES
|
|
||||||
|
|
||||||
Note: We recommend developers to use either objectData or GeneralData as we provide inbuilt data synchronization with cloud in them. If developer wants to receive response across all TYPES, or he is interested in doing request response management of the promise himself, he should use Application Data. Rad more in next section.
|
|
||||||
|
|
||||||
Note: Application Data results are not stored in local IndexedDB by Standard Operations Framework.
|
|
||||||
|
|
||||||
Note: If a blank REQUEST APPLICATION DATA is made, then cloud will give all application data at the admin ID of the application
|
|
||||||
|
|
||||||
Note: Application field is used by the Cloud to judge whether this message should be deleted after 7 days, or stored permanently. Developers should be careful not to change value of application field if the default value enables the message to be stored permanently. Currently messages sent from subadmins to any receiverID for applications notified by the cloud in the FLO Blockchain are stored permanently. So do not change application field as a caution. Use the comment field.
|
|
||||||
|
|
||||||
# Examples for FLO Cloud data operations
|
|
||||||
|
|
||||||
## 1. Data fields stored in each of decentralised servers
|
## 1. Data fields stored in each of decentralised servers
|
||||||
|
|
||||||
@ -825,115 +550,66 @@ It allows the clients to retrieve messages before and after a certain time, and
|
|||||||
* `Object Data`: Message field can only be a JavaScript Object
|
* `Object Data`: Message field can only be a JavaScript Object
|
||||||
|
|
||||||
### System Data Type
|
### System Data Type
|
||||||
* `Application Data`: Application data is the base on which General Data system and Object Data system has been created. The formats for General Data and Application Data are exactly the same exfept type is not mandatory in SEND APPLICATION DATA. This should be used if developer wants to do his own response data management, or wants no cloud-client data synchronization.
|
* `Application Data`: Application data is the base on which General Data system and Object Data system has been created. The formats for General Data and Application Data are exactly the same. Users will never need to use Application Data. So we have deprecated Application Data.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
* `General Data:` Type field is mandatory
|
* `General Data:` Type field is mandatory
|
||||||
* `Object Data:` Type field has been consumed to create object functionality
|
* `Object Data:` Type field has been consumed to create object functionality
|
||||||
|
|
||||||
|
|
||||||
|
## 4. General Data
|
||||||
|
|
||||||
#### Philosophy of Application Data
|
### SEND DATA
|
||||||
Application data system is original data field system that we created for our cloud. It does not integrate with local IDB storage and synchronization in the browser. In our development process, General Data was created by adding vector clock support to application data at browser level to synchronize cloud and local IDB. SEND and REQUEST options in Application Data are exactly the same as General data without vector clock IDB local storage support at browser level.
|
Parameters while sending
|
||||||
|
|
||||||
Even ObjectData was created on top of Application Data. ObjectData is a special construction provided by framework so that Javacript objects can directly be stored and retrieved from the cloud
|
* `Message`: Actual Message to be sent
|
||||||
|
* `Type`: User defined type (anything that user wants to classify as type)
|
||||||
|
|
||||||
#### Vector Clock issues in Application Data
|
#### Options
|
||||||
Application data supports Vector clock in REQUEST option, but it is not mandatory. Since Application Data system has no mandatory vector clock requirement in REQUEST OPTIONS, it will always give the entire data set stored in the cloud since start if invoked without vectorClock, and user will have to custom handle the request output himself at client end. Our client side framework will not store it for the user. However the application data request invoked with vectorClock, then our cloud will give data after the vectorClock
|
* `receiverID` - receiver of the data
|
||||||
|
* `application` - application using the data
|
||||||
|
* `comment` - user comment of the data
|
||||||
|
|
||||||
Usually ObjectData and GeneralData systems will support most of user needs. But for cases when the user wants the entire cloud data set, and no client side framework handling, he should use ApplicationData. Although Application Data system supports lower vectorClock, upper vectorClock, at vectorClock and mostRecentvectorClock as a REQUEST option, but the processing of these have to be done by user. Unlike in case of ObjectData and GeneralData, the output of Request Application Data is not available in floGlobals.
|
### REQUEST DATA
|
||||||
|
|
||||||
* If lower vectorClock is specified, it will give all cloud stored application data after that vectorClock.
|
#### request options
|
||||||
* If upper vectorClock is specified, it will give all cloud stored application data before that vectorClock.
|
* `receiverID` - receiver of the data
|
||||||
* If at vectorClock is specified, it will give cloud stored application data exactly at that vectorClock.
|
* `type` - type of the data ( a free user field)
|
||||||
* If most recent vectorClock is set as true, it will give just the last stored application data.
|
* `senderIDs` - array of senderIDs
|
||||||
* If no vectorClock is specified, it will give all the data stored in cloud
|
* `application` - application of the data
|
||||||
|
* `comment` - comment of the data
|
||||||
#### No local IDB storage
|
* `lowerVectorClock` - VC from which the data is to be requested
|
||||||
Application Data results are not stored in local IndexedDB by Standard Operations Framework.
|
* `upperVectorClock` - VC till which the data is to be requested
|
||||||
|
* `atVectorClock` - VC at which the data is to requested
|
||||||
## FLO Decentralised Applications (Dapps)
|
* `mostRecent` - boolean (true: request only the recent data matching the pattern)
|
||||||
`floDapps` module contains methods for basic Dapp. floDapps uses all of the above modules.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### setCustomPrivKeyInput
|
|
||||||
floDapps.setCustomPrivKeyInput(customFn)
|
|
||||||
`setCustomPrivKeyInput` adds a startup funtion to the Dapp
|
|
||||||
1. customFn - custom function to get login credentials (privateKey)
|
|
||||||
|
|
||||||
|
|
||||||
#### manageSubAdmins
|
|
||||||
floDapps.manageSubAdmins(adminPrivKey, addList, rmList)
|
|
||||||
`manageSubAdmins` adds and/or removes subAdmins by sending transaction to blockchain.
|
|
||||||
1. adminPrivKey - private key of the adminID
|
|
||||||
2. addList - Array of subAdmins to add
|
|
||||||
3. rmList - Array of subAdmins to remove
|
|
||||||
|
|
||||||
|
|
||||||
* Example: floDapps.manageSubAdmins(adminPrivKey, [addr1, addr2..]) to add addr1 and addr2 as subAdmin
|
|
||||||
* Example: floDapps.manageSubAdmins(adminPrivKey, null, [addr1, addr2..]) to remove addr1 and addr2 as subAdmin
|
|
||||||
* This command must be run from the console of app page only.
|
|
||||||
|
|
||||||
#### clearCredentials
|
|
||||||
floDapps.clearCredentials()
|
|
||||||
`clearCredentials` removes the login credentials stored in IDB.
|
|
||||||
|
|
||||||
#### securePrivKey
|
|
||||||
floDapps.securePrivKey(pwd)
|
|
||||||
`securePrivKey` replaces the stored private key with an encrypted variant
|
|
||||||
1. pwd - password for the encryption
|
|
||||||
Note: if securePrivKey is used, then password must be requested during customPrivKeyInput (in setCustomPrivKeyInput).
|
|
||||||
|
|
||||||
#### getNextGeneralData
|
|
||||||
floDapps.getNextGeneralData(type, vectorClock, options = {})
|
|
||||||
`getNextGeneralData` return the next generaldata
|
|
||||||
1. type - type of the general data
|
|
||||||
2. vectorClock - current known VC, from which next data to be retrived
|
|
||||||
3. options - (optional, {comment, application} of the data)
|
|
||||||
|
|
||||||
#### launchStartUp
|
|
||||||
floDapps.launchStartUp()
|
|
||||||
`launchStartUp` launchs the Dapp startup functions
|
|
||||||
|
|
||||||
##### reactorEvents
|
|
||||||
* startUpSuccessLog - called when a startup funtion is successfully completed
|
|
||||||
* startUpErrorLog - called when a startup funtion encounters an error
|
|
||||||
|
|
||||||
##### onLoadStartUp
|
|
||||||
Sample startup is defined in onLoadStartUp function
|
|
||||||
|
|
||||||
#### addStartUpFunction
|
|
||||||
floDapps.addStartUpFunction(fname, fn)
|
|
||||||
`addStartUpFunction` adds a startup funtion to the Dapp
|
|
||||||
1. fname - Name of the startup function
|
|
||||||
2. fn - body of the function
|
|
||||||
Note: startup funtions are called in parallel. Therefore only add custom startup funtion only if it can run in parallel with other startup functions. (default startup funtions are read supernode list and subAdmin list from blockchain API, load data from indexedDB, get login credentials)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
//Executes automatically on startup before anything else executes
|
|
||||||
floDapps.addStartUpFunction("myFirstFunction",
|
|
||||||
function (){
|
|
||||||
return new Promise (
|
|
||||||
(resolve,reject)=>{
|
|
||||||
console.log("First function Executed before everything else");
|
|
||||||
resolve("My First Function executed")
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Manual execution on console
|
|
||||||
floDapps.util.startUpFunctions.myFirstFunction();
|
|
||||||
```
|
|
||||||
|
|
||||||
Philosophy: Suppose you want a function that must run before any of standard operation function runs. Then you need to first create a promisified version of that function
|
|
||||||
|
|
||||||
### Advanced Dapp functions usually not needed for users
|
|
||||||
|
|
||||||
#### setAppObjectStores
|
|
||||||
floDapps.setAppObjectStores(appObs)
|
|
||||||
`setAppObjectStores` adds additionals objectstores for the app
|
|
||||||
1. appObs - additionals objects for the app
|
|
||||||
|
|
||||||
|
## 5. ObjectData
|
||||||
|
|
||||||
|
### RESET or UPDATE operations
|
||||||
|
Parameters while resetting or updating
|
||||||
|
* `Object Data`
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
* `receiverID` - receiver of the data
|
||||||
|
* `application` - application using the data
|
||||||
|
* `comment` - comment of the data
|
||||||
|
|
||||||
|
### REQUEST DATA
|
||||||
|
|
||||||
|
#### request options
|
||||||
|
* `receiverID` - receiver of the data
|
||||||
|
* `senderIDs` - array of senderIDs
|
||||||
|
* `application` - application of the data
|
||||||
|
* `comment` - comment of the data
|
||||||
|
* `lowerVectorClock` - VC from which the data is to be requested
|
||||||
|
* `upperVectorClock` - VC till which the data is to be requested
|
||||||
|
* `atVectorClock` - VC at which the data is to requested
|
||||||
|
* `mostRecent` - boolean (true: request only the recent data matching the pattern)
|
||||||
|
|
||||||
|
## 5. Application Data
|
||||||
|
|
||||||
|
### DEPRECATED
|
||||||
|
|
||||||
|
Application data system is a legacy data field without vector clock support in options. In our development process, General Data was created by adding vector clock support to application data at user level. So SEND and REQUEST options in Application Data are exactly the same as General data without vector clock options
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1135
btcOperator.js
1135
btcOperator.js
File diff suppressed because it is too large
Load Diff
257
compactIDB.js
257
compactIDB.js
@ -1,257 +0,0 @@
|
|||||||
(function (EXPORTS) { //compactIDB v2.1.2
|
|
||||||
/* 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) => true);
|
|
||||||
options.limit = options.limit || false;
|
|
||||||
options.reverse = options.reverse || 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 || options.reverse ? "prev" : "next");
|
|
||||||
curReq.onsuccess = (evt) => {
|
|
||||||
var cursor = evt.target.result;
|
|
||||||
if (!cursor || (options.limit && options.limit <= Object.keys(filteredResult).length))
|
|
||||||
return resolve(filteredResult); //reached end of key list or limit reached
|
|
||||||
else if (options.patternEval(cursor.primaryKey, cursor.value)) {
|
|
||||||
filteredResult[cursor.primaryKey] = cursor.value;
|
|
||||||
options.lastOnly ? resolve(filteredResult) : cursor.continue();
|
|
||||||
} else
|
|
||||||
cursor.continue();
|
|
||||||
}
|
|
||||||
curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`);
|
|
||||||
db.close();
|
|
||||||
}).catch(error => reject(error));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
})(window.compactIDB = {});
|
|
||||||
1044
floBlockchainAPI.js
1044
floBlockchainAPI.js
File diff suppressed because it is too large
Load Diff
1106
floCloudAPI.js
1106
floCloudAPI.js
File diff suppressed because it is too large
Load Diff
530
floCrypto.js
530
floCrypto.js
@ -1,530 +0,0 @@
|
|||||||
(function (EXPORTS) { //floCrypto v2.3.6a
|
|
||||||
/* 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"));
|
|
||||||
coinjs.compressed = true; //defaulting coinjs compressed to true;
|
|
||||||
|
|
||||||
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(securedMathRandom() * (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(securedMathRandom() * 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);
|
|
||||||
var messageHash = Crypto.SHA256(data);
|
|
||||||
var messageSign = Bitcoin.ECDSA.sign(messageHash, 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 sigBytes = Crypto.util.hexToBytes(signatureHex);
|
|
||||||
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
|
||||||
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, 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.defineProperties(floCrypto, {
|
|
||||||
newID: {
|
|
||||||
get: () => generateNewID()
|
|
||||||
},
|
|
||||||
hashID: {
|
|
||||||
value: (str) => {
|
|
||||||
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), { asBytes: true });
|
|
||||||
bytes.unshift(bitjs.pub);
|
|
||||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
|
||||||
asBytes: true
|
|
||||||
}), {
|
|
||||||
asBytes: true
|
|
||||||
});
|
|
||||||
var checksum = hash.slice(0, 4);
|
|
||||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tmpID: {
|
|
||||||
get: () => {
|
|
||||||
let bytes = Crypto.util.randomBytes(20);
|
|
||||||
bytes.unshift(bitjs.pub);
|
|
||||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
|
||||||
asBytes: true
|
|
||||||
}), {
|
|
||||||
asBytes: true
|
|
||||||
});
|
|
||||||
var checksum = hash.slice(0, 4);
|
|
||||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
floCrypto.getAddress = function (privateKeyHex, strict = false) {
|
|
||||||
if (!privateKeyHex)
|
|
||||||
return;
|
|
||||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
|
||||||
if (key.priv == null)
|
|
||||||
return null;
|
|
||||||
key.setCompressed(true);
|
|
||||||
let pubKey = key.getPubKeyHex(),
|
|
||||||
version = bitjs.Base58.decode(privateKeyHex)[0];
|
|
||||||
switch (version) {
|
|
||||||
case coinjs.priv: //BTC
|
|
||||||
return coinjs.bech32Address(pubKey).address;
|
|
||||||
case bitjs.priv: //FLO
|
|
||||||
return bitjs.pubkey2address(pubKey);
|
|
||||||
default:
|
|
||||||
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//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.toUpperCase() == key.getPubKeyHex().toUpperCase())
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
|
|
||||||
if (!Array.isArray(publicKeyList) || !publicKeyList.length)
|
|
||||||
return null;
|
|
||||||
if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length)
|
|
||||||
return null;
|
|
||||||
try {
|
|
||||||
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
|
|
||||||
return multisig;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
floCrypto.decodeRedeemScript = function (redeemScript) {
|
|
||||||
try {
|
|
||||||
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
|
|
||||||
return decoded;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if the given flo-id is valid or not
|
|
||||||
floCrypto.validateFloID = function (floID, regularOnly = false) {
|
|
||||||
if (!floID)
|
|
||||||
return false;
|
|
||||||
try {
|
|
||||||
let addr = new Bitcoin.Address(floID);
|
|
||||||
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if the given address (any blockchain) is valid or not
|
|
||||||
floCrypto.validateAddr = function (address, std = true, bech = true) {
|
|
||||||
let raw = decodeAddress(address);
|
|
||||||
if (!raw)
|
|
||||||
return false;
|
|
||||||
if (typeof raw.version !== 'undefined') { //legacy or segwit
|
|
||||||
if (std == false)
|
|
||||||
return false;
|
|
||||||
else if (std === true || (!Array.isArray(std) && std === raw.version) || (Array.isArray(std) && std.includes(raw.version)))
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
} else if (typeof raw.bech_version !== 'undefined') { //bech32
|
|
||||||
if (bech === false)
|
|
||||||
return false;
|
|
||||||
else if (bech === true || (!Array.isArray(bech) && bech === raw.bech_version) || (Array.isArray(bech) && bech.includes(raw.bech_version)))
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
} else //unknown
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check the public-key (or redeem-script) for the address (any blockchain)
|
|
||||||
floCrypto.verifyPubKey = function (pubKeyHex, address) {
|
|
||||||
let raw = decodeAddress(address);
|
|
||||||
if (!raw)
|
|
||||||
return;
|
|
||||||
let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })));
|
|
||||||
if (typeof raw.bech_version !== 'undefined' && raw.bytes.length == 32) //bech32-multisig
|
|
||||||
raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true }));
|
|
||||||
return pub_hash === raw.hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Convert the given address (any blockchain) to equivalent floID
|
|
||||||
floCrypto.toFloID = function (address, options = null) {
|
|
||||||
if (!address)
|
|
||||||
return;
|
|
||||||
let raw = decodeAddress(address);
|
|
||||||
if (!raw)
|
|
||||||
return;
|
|
||||||
else if (options) { //if (optional) version check is passed
|
|
||||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
|
||||||
return;
|
|
||||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
raw.bytes.unshift(bitjs.pub);
|
|
||||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
|
||||||
asBytes: true
|
|
||||||
}), {
|
|
||||||
asBytes: true
|
|
||||||
});
|
|
||||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Convert raw address bytes to floID
|
|
||||||
floCrypto.rawToFloID = function (raw_bytes) {
|
|
||||||
if (typeof raw_bytes === 'string')
|
|
||||||
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
|
|
||||||
if (raw_bytes.length != 20)
|
|
||||||
return null;
|
|
||||||
raw_bytes.unshift(bitjs.pub);
|
|
||||||
let hash = Crypto.SHA256(Crypto.SHA256(raw_bytes, {
|
|
||||||
asBytes: true
|
|
||||||
}), {
|
|
||||||
asBytes: true
|
|
||||||
});
|
|
||||||
return bitjs.Base58.encode(raw_bytes.concat(hash.slice(0, 4)));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Convert the given multisig address (any blockchain) to equivalent multisig floID
|
|
||||||
floCrypto.toMultisigFloID = function (address, options = null) {
|
|
||||||
if (!address)
|
|
||||||
return;
|
|
||||||
let raw = decodeAddress(address);
|
|
||||||
if (!raw)
|
|
||||||
return;
|
|
||||||
else if (options) { //if (optional) version check is passed
|
|
||||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
|
||||||
return;
|
|
||||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof raw.bech_version !== 'undefined') {
|
|
||||||
if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes
|
|
||||||
//multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech
|
|
||||||
raw.bytes = ripemd160(raw.bytes, {
|
|
||||||
asBytes: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
raw.bytes.unshift(bitjs.multisig);
|
|
||||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
|
||||||
asBytes: true
|
|
||||||
}), {
|
|
||||||
asBytes: true
|
|
||||||
});
|
|
||||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
|
|
||||||
floCrypto.isSameAddr = function (addr1, addr2) {
|
|
||||||
if (!addr1 || !addr2)
|
|
||||||
return;
|
|
||||||
let raw1 = decodeAddress(addr1),
|
|
||||||
raw2 = decodeAddress(addr2);
|
|
||||||
if (!raw1 || !raw2)
|
|
||||||
return false;
|
|
||||||
else {
|
|
||||||
if (typeof raw1.bech_version !== 'undefined' && raw1.bytes.length == 32) //bech32-multisig
|
|
||||||
raw1.hex = Crypto.util.bytesToHex(ripemd160(raw1.bytes, { asBytes: true }));
|
|
||||||
if (typeof raw2.bech_version !== 'undefined' && raw2.bytes.length == 32) //bech32-multisig
|
|
||||||
raw2.hex = Crypto.util.bytesToHex(ripemd160(raw2.bytes, { asBytes: true }));
|
|
||||||
return raw1.hex === raw2.hex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodeAddress = floCrypto.decodeAddr = function (address) {
|
|
||||||
if (!address)
|
|
||||||
return;
|
|
||||||
else if (address.length == 33 || address.length == 34) { //legacy encoding
|
|
||||||
let decode = bitjs.Base58.decode(address);
|
|
||||||
let bytes = decode.slice(0, decode.length - 4);
|
|
||||||
let checksum = decode.slice(decode.length - 4),
|
|
||||||
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
|
||||||
asBytes: true
|
|
||||||
}), {
|
|
||||||
asBytes: true
|
|
||||||
});
|
|
||||||
return (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) ? null : {
|
|
||||||
version: bytes.shift(),
|
|
||||||
hex: Crypto.util.bytesToHex(bytes),
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
} else if (address.length == 42 || address.length == 62) { //bech encoding
|
|
||||||
let decode = coinjs.bech32_decode(address);
|
|
||||||
if (decode) {
|
|
||||||
let bytes = decode.data;
|
|
||||||
let bech_version = bytes.shift();
|
|
||||||
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
|
|
||||||
return {
|
|
||||||
bech_version,
|
|
||||||
hrp: decode.hrp,
|
|
||||||
hex: Crypto.util.bytesToHex(bytes),
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//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 = {});
|
|
||||||
843
floDapps.js
843
floDapps.js
@ -1,843 +0,0 @@
|
|||||||
(function (EXPORTS) { //floDapps v2.4.1
|
|
||||||
/* General functions for FLO Dapps*/
|
|
||||||
'use strict';
|
|
||||||
const floDapps = EXPORTS;
|
|
||||||
|
|
||||||
const DEFAULT = {
|
|
||||||
root: "floDapps",
|
|
||||||
application: floGlobals.application,
|
|
||||||
adminID: floGlobals.adminID
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.defineProperties(floDapps, {
|
|
||||||
application: {
|
|
||||||
get: () => DEFAULT.application
|
|
||||||
},
|
|
||||||
adminID: {
|
|
||||||
get: () => DEFAULT.adminID
|
|
||||||
},
|
|
||||||
root: {
|
|
||||||
get: () => DEFAULT.root
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var user_priv_raw, aes_key, user_priv_wrap; //private variable inside capsule
|
|
||||||
const raw_user = {
|
|
||||||
get private() {
|
|
||||||
if (!user_priv_raw)
|
|
||||||
throw "User not logged in";
|
|
||||||
return Crypto.AES.decrypt(user_priv_raw, aes_key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var user_id, user_public, user_private;
|
|
||||||
const user = floDapps.user = {
|
|
||||||
get id() {
|
|
||||||
if (!user_id)
|
|
||||||
throw "User not logged in";
|
|
||||||
return user_id;
|
|
||||||
},
|
|
||||||
get public() {
|
|
||||||
if (!user_public)
|
|
||||||
throw "User not logged in";
|
|
||||||
return user_public;
|
|
||||||
},
|
|
||||||
get private() {
|
|
||||||
if (!user_private)
|
|
||||||
throw "User not logged in";
|
|
||||||
else if (user_private instanceof Function)
|
|
||||||
return user_private();
|
|
||||||
else
|
|
||||||
return Crypto.AES.decrypt(user_private, aes_key);
|
|
||||||
},
|
|
||||||
sign(message) {
|
|
||||||
return floCrypto.signData(message, raw_user.private);
|
|
||||||
},
|
|
||||||
decrypt(data) {
|
|
||||||
return floCrypto.decryptData(data, raw_user.private);
|
|
||||||
},
|
|
||||||
encipher(message) {
|
|
||||||
return Crypto.AES.encrypt(message, raw_user.private);
|
|
||||||
},
|
|
||||||
decipher(data) {
|
|
||||||
return Crypto.AES.decrypt(data, raw_user.private);
|
|
||||||
},
|
|
||||||
get db_name() {
|
|
||||||
return "floDapps#" + floCrypto.toFloID(user.id);
|
|
||||||
},
|
|
||||||
lock() {
|
|
||||||
user_private = user_priv_wrap;
|
|
||||||
},
|
|
||||||
async unlock() {
|
|
||||||
if (await user.private === raw_user.private)
|
|
||||||
user_private = user_priv_raw;
|
|
||||||
},
|
|
||||||
get_contact(id) {
|
|
||||||
if (!user.contacts)
|
|
||||||
throw "Contacts not available";
|
|
||||||
else if (user.contacts[id])
|
|
||||||
return user.contacts[id];
|
|
||||||
else {
|
|
||||||
let id_raw = floCrypto.decodeAddr(id).hex;
|
|
||||||
for (let i in user.contacts)
|
|
||||||
if (floCrypto.decodeAddr(i).hex == id_raw)
|
|
||||||
return user.contacts[i];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get_pubKey(id) {
|
|
||||||
if (!user.pubKeys)
|
|
||||||
throw "Contacts not available";
|
|
||||||
else if (user.pubKeys[id])
|
|
||||||
return user.pubKeys[id];
|
|
||||||
else {
|
|
||||||
let id_raw = floCrypto.decodeAddr(id).hex;
|
|
||||||
for (let i in user.pubKeys)
|
|
||||||
if (floCrypto.decodeAddr(i).hex == id_raw)
|
|
||||||
return user.pubKeys[i];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clear() {
|
|
||||||
user_id = user_public = user_private = undefined;
|
|
||||||
user_priv_raw = aes_key = undefined;
|
|
||||||
delete user.contacts;
|
|
||||||
delete user.pubKeys;
|
|
||||||
delete user.messages;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.defineProperties(window, {
|
|
||||||
myFloID: {
|
|
||||||
get: () => {
|
|
||||||
try {
|
|
||||||
return user.id;
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
myUserID: {
|
|
||||||
get: () => {
|
|
||||||
try {
|
|
||||||
return user.id;
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
myPubKey: {
|
|
||||||
get: () => {
|
|
||||||
try {
|
|
||||||
return user.public;
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
myPrivKey: {
|
|
||||||
get: () => {
|
|
||||||
try {
|
|
||||||
return user.private;
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var subAdmins, trustedIDs, settings;
|
|
||||||
Object.defineProperties(floGlobals, {
|
|
||||||
subAdmins: {
|
|
||||||
get: () => subAdmins
|
|
||||||
},
|
|
||||||
trustedIDs: {
|
|
||||||
get: () => trustedIDs
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
get: () => settings
|
|
||||||
},
|
|
||||||
contacts: {
|
|
||||||
get: () => user.contacts
|
|
||||||
},
|
|
||||||
pubKeys: {
|
|
||||||
get: () => user.pubKeys
|
|
||||||
},
|
|
||||||
messages: {
|
|
||||||
get: () => user.messages
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function initIndexedDB() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var obs_g = {
|
|
||||||
//general
|
|
||||||
lastTx: {},
|
|
||||||
//supernode (cloud list)
|
|
||||||
supernodes: {}
|
|
||||||
}
|
|
||||||
var obs_a = {
|
|
||||||
//login credentials
|
|
||||||
credentials: {},
|
|
||||||
//for Dapps
|
|
||||||
subAdmins: {},
|
|
||||||
trustedIDs: {},
|
|
||||||
settings: {},
|
|
||||||
appObjects: {},
|
|
||||||
generalData: {},
|
|
||||||
lastVC: {}
|
|
||||||
}
|
|
||||||
//add other given objectStores
|
|
||||||
initIndexedDB.appObs = initIndexedDB.appObs || {}
|
|
||||||
for (let o in initIndexedDB.appObs)
|
|
||||||
if (!(o in obs_a))
|
|
||||||
obs_a[o] = initIndexedDB.appObs[o]
|
|
||||||
Promise.all([
|
|
||||||
compactIDB.initDB(DEFAULT.application, obs_a),
|
|
||||||
compactIDB.initDB(DEFAULT.root, obs_g)
|
|
||||||
]).then(result => {
|
|
||||||
compactIDB.setDefaultDB(DEFAULT.application)
|
|
||||||
resolve("IndexedDB App Storage Initated Successfully")
|
|
||||||
}).catch(error => reject(error));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function initUserDB() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var obs = {
|
|
||||||
contacts: {},
|
|
||||||
pubKeys: {},
|
|
||||||
messages: {}
|
|
||||||
}
|
|
||||||
compactIDB.initDB(user.db_name, obs).then(result => {
|
|
||||||
resolve("UserDB Initated Successfully")
|
|
||||||
}).catch(error => reject('Init userDB failed'));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadUserDB() {
|
|
||||||
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], user.db_name)
|
|
||||||
Promise.all(promises).then(results => {
|
|
||||||
for (var i = 0; i < loadData.length; i++)
|
|
||||||
user[loadData[i]] = results[i]
|
|
||||||
resolve("Loaded Data from userDB")
|
|
||||||
}).catch(error => reject('Load userDB failed'))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const startUpOptions = {
|
|
||||||
cloud: true,
|
|
||||||
app_config: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
floDapps.startUpOptions = {
|
|
||||||
set app_config(val) {
|
|
||||||
if (val === true || val === false)
|
|
||||||
startUpOptions.app_config = val;
|
|
||||||
},
|
|
||||||
get app_config() { return startUpOptions.app_config },
|
|
||||||
|
|
||||||
set cloud(val) {
|
|
||||||
if (val === true || val === false)
|
|
||||||
startUpOptions.cloud = val;
|
|
||||||
},
|
|
||||||
get cloud() { return startUpOptions.cloud },
|
|
||||||
}
|
|
||||||
|
|
||||||
const startUpFunctions = [];
|
|
||||||
|
|
||||||
startUpFunctions.push(function readSupernodeListFromAPI() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!startUpOptions.cloud)
|
|
||||||
return resolve("No cloud for this app");
|
|
||||||
const CLOUD_KEY = "floCloudAPI#" + floCloudAPI.SNStorageID;
|
|
||||||
compactIDB.readData("lastTx", CLOUD_KEY, DEFAULT.root).then(lastTx => {
|
|
||||||
var query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName };
|
|
||||||
if (typeof lastTx == 'number') //lastTx is tx count (*backward support)
|
|
||||||
query_options.ignoreOld = lastTx;
|
|
||||||
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 => {
|
|
||||||
compactIDB.readData("supernodes", CLOUD_KEY, DEFAULT.root).then(nodes => {
|
|
||||||
nodes = nodes || {};
|
|
||||||
for (var i = result.data.length - 1; i >= 0; i--) {
|
|
||||||
var content = JSON.parse(result.data[i])[floCloudAPI.SNStorageName];
|
|
||||||
for (let sn in content.removeNodes)
|
|
||||||
delete nodes[sn];
|
|
||||||
for (let sn in content.newNodes)
|
|
||||||
nodes[sn] = content.newNodes[sn];
|
|
||||||
for (let sn in content.updateNodes)
|
|
||||||
if (sn in nodes) //check if node is listed
|
|
||||||
nodes[sn].uri = content.updateNodes[sn];
|
|
||||||
}
|
|
||||||
Promise.all([
|
|
||||||
compactIDB.writeData("lastTx", result.lastItem, CLOUD_KEY, DEFAULT.root),
|
|
||||||
compactIDB.writeData("supernodes", nodes, CLOUD_KEY, DEFAULT.root)
|
|
||||||
]).then(_ => {
|
|
||||||
floCloudAPI.init(nodes)
|
|
||||||
.then(result => resolve("Loaded Supernode list\n" + result))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
})
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
startUpFunctions.push(function readAppConfigFromAPI() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
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
|
|
||||||
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;
|
|
||||||
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 (Array.isArray(content.removeTrustedID))
|
|
||||||
for (var 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++)
|
|
||||||
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("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))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
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))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
var keyInput = type => new Promise((resolve, reject) => {
|
|
||||||
let inputVal = prompt(`Enter ${type}: `)
|
|
||||||
if (inputVal === null)
|
|
||||||
reject(null)
|
|
||||||
else
|
|
||||||
resolve(inputVal)
|
|
||||||
});
|
|
||||||
|
|
||||||
function getCredentials() {
|
|
||||||
|
|
||||||
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(`${DEFAULT.application}#privKey`)
|
|
||||||
if (indexArr) {
|
|
||||||
readSharesFromIDB(JSON.parse(indexArr))
|
|
||||||
.then(result => resolve(result))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
} else {
|
|
||||||
var privKey;
|
|
||||||
keyInput("PRIVATE_KEY").then(result => {
|
|
||||||
if (!result)
|
|
||||||
return reject("Empty Private Key")
|
|
||||||
var floID = floCrypto.getFloID(result)
|
|
||||||
if (!floID || !floCrypto.validateFloID(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(`${DEFAULT.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 {
|
|
||||||
keyInput("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 {
|
|
||||||
user_public = floCrypto.getPubKeyHex(privKey);
|
|
||||||
user_id = floCrypto.getAddress(privKey);
|
|
||||||
if (startUpOptions.cloud)
|
|
||||||
floCloudAPI.user(user_id, privKey); //Set user for floCloudAPI
|
|
||||||
user_priv_wrap = () => checkIfPinRequired(key);
|
|
||||||
let n = floCrypto.randInt(12, 20);
|
|
||||||
aes_key = floCrypto.randString(n);
|
|
||||||
user_priv_raw = Crypto.AES.encrypt(privKey, aes_key);
|
|
||||||
user_private = user_priv_wrap;
|
|
||||||
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()).then(r => {
|
|
||||||
callAndLog(loadUserDB())
|
|
||||||
.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 => {
|
|
||||||
startUpLog(false, 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 ? keyInput = 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, user.db_name).then(result => {
|
|
||||||
user.contacts[floID] = name;
|
|
||||||
resolve("Contact stored")
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
floDapps.storePubKey = function (floID, pubKey) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (floID in user.pubKeys)
|
|
||||||
return resolve("pubKey already stored")
|
|
||||||
if (!floCrypto.validateAddr(floID))
|
|
||||||
return reject("Invalid floID!")
|
|
||||||
if (!floCrypto.verifyPubKey(pubKey, floID))
|
|
||||||
return reject("Incorrect pubKey")
|
|
||||||
compactIDB.writeData("pubKeys", pubKey, floID, user.db_name).then(result => {
|
|
||||||
user.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: DEFAULT.root,
|
|
||||||
comment: DEFAULT.application
|
|
||||||
}
|
|
||||||
if (floID in user.pubKeys)
|
|
||||||
message = floCrypto.encryptData(JSON.stringify(message), user.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(user.messages).sort().pop()
|
|
||||||
let options = {
|
|
||||||
receiverID: user.id,
|
|
||||||
application: DEFAULT.root,
|
|
||||||
lowerVectorClock: lastVC + 1
|
|
||||||
}
|
|
||||||
let privKey = raw_user.private;
|
|
||||||
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, privKey)
|
|
||||||
} catch (error) { }
|
|
||||||
compactIDB.writeData("messages", d[v], v, user.db_name)
|
|
||||||
user.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 (!startUpOptions.app_config)
|
|
||||||
return reject("No configs for this app");
|
|
||||||
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 = {
|
|
||||||
[DEFAULT.application]: {
|
|
||||||
addSubAdmin: addList,
|
|
||||||
removeSubAdmin: rmList,
|
|
||||||
settings: settings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var floID = floCrypto.getFloID(adminPrivKey)
|
|
||||||
if (floID != DEFAULT.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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
floDapps.manageAppTrustedIDs = function (adminPrivKey, addList, rmList) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!startUpOptions.app_config)
|
|
||||||
return reject("No configs for this app");
|
|
||||||
if (!Array.isArray(addList) || !addList.length) addList = undefined;
|
|
||||||
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
|
|
||||||
if (!addList && !rmList)
|
|
||||||
return reject("No change in list")
|
|
||||||
var floData = {
|
|
||||||
[DEFAULT.application]: {
|
|
||||||
addTrustedID: addList,
|
|
||||||
removeTrustedID: rmList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var floID = floCrypto.getFloID(adminPrivKey)
|
|
||||||
if (floID != DEFAULT.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', DEFAULT.application).then(result => {
|
|
||||||
localStorage.removeItem(`${DEFAULT.application}#privKey`);
|
|
||||||
user.clear();
|
|
||||||
resolve("privKey credentials deleted!")
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
floDapps.deleteUserData = function (credentials = false) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let p = []
|
|
||||||
p.push(compactIDB.deleteDB(user.db_name))
|
|
||||||
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(DEFAULT.application).then(result => {
|
|
||||||
localStorage.removeItem(`${DEFAULT.application}#privKey`)
|
|
||||||
user.clear();
|
|
||||||
compactIDB.removeData('lastTx', `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root)
|
|
||||||
.then(result => resolve("App database(local) deleted"))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
floDapps.securePrivKey = function (pwd) {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
let indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
|
||||||
if (!indexArr)
|
|
||||||
return reject("PrivKey not found");
|
|
||||||
indexArr = JSON.parse(indexArr)
|
|
||||||
let encryptedKey = Crypto.AES.encrypt(await user.private, pwd);
|
|
||||||
let threshold = indexArr.length;
|
|
||||||
let shares = floCrypto.createShamirsSecretShares(encryptedKey, threshold, threshold)
|
|
||||||
let promises = [];
|
|
||||||
let overwriteFn = (share, index) =>
|
|
||||||
compactIDB.writeData("credentials", share, index, DEFAULT.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(`${DEFAULT.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) ? raw_user.private : 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: user.contacts,
|
|
||||||
pubKeys: user.pubKeys,
|
|
||||||
messages: user.messages
|
|
||||||
}
|
|
||||||
let message = Crypto.AES.encrypt(JSON.stringify(sync), raw_user.private)
|
|
||||||
let options = {
|
|
||||||
receiverID: user.id,
|
|
||||||
application: DEFAULT.root
|
|
||||||
}
|
|
||||||
floCloudAPI.sendApplicationData(message, "syncData", options)
|
|
||||||
.then(result => resolve(result))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
});
|
|
||||||
|
|
||||||
syncData.newDevice = () => new Promise((resolve, reject) => {
|
|
||||||
var options = {
|
|
||||||
receiverID: user.id,
|
|
||||||
senderID: user.id,
|
|
||||||
application: DEFAULT.root,
|
|
||||||
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, raw_user.private))
|
|
||||||
let promises = []
|
|
||||||
let store = (key, val, obs) => promises.push(compactIDB.writeData(obs, val, key, user.db_name));
|
|
||||||
["contacts", "pubKeys", "messages"].forEach(c => {
|
|
||||||
for (let i in sync[c]) {
|
|
||||||
store(i, sync[c][i], c)
|
|
||||||
user[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 = {});
|
|
||||||
166
floTokenAPI.js
166
floTokenAPI.js
@ -1,166 +0,0 @@
|
|||||||
(function (EXPORTS) { //floTokenAPI v1.0.4a
|
|
||||||
/* Token Operator to send/receive tokens via blockchain using API calls*/
|
|
||||||
'use strict';
|
|
||||||
const tokenAPI = EXPORTS;
|
|
||||||
|
|
||||||
const DEFAULT = {
|
|
||||||
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
|
|
||||||
currency: floGlobals.currency || "rupee"
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperties(tokenAPI, {
|
|
||||||
URL: {
|
|
||||||
get: () => DEFAULT.apiURL
|
|
||||||
},
|
|
||||||
currency: {
|
|
||||||
get: () => DEFAULT.currency,
|
|
||||||
set: currency => DEFAULT.currency = currency
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (floGlobals.currency) tokenAPI.currency = floGlobals.currency;
|
|
||||||
|
|
||||||
Object.defineProperties(floGlobals, {
|
|
||||||
currency: {
|
|
||||||
get: () => DEFAULT.currency,
|
|
||||||
set: currency => DEFAULT.currency = currency
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetch_api = tokenAPI.fetch = function (apicall) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
console.debug(DEFAULT.apiURL + apicall);
|
|
||||||
fetch(DEFAULT.apiURL + apicall).then(response => {
|
|
||||||
if (response.ok)
|
|
||||||
response.json().then(data => resolve(data));
|
|
||||||
else
|
|
||||||
reject(response)
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBalance = tokenAPI.getBalance = function (floID, token = DEFAULT.currency) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
|
|
||||||
.then(result => resolve(result.balance || 0))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenAPI.getTx = function (txID) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => {
|
|
||||||
if (res.result === "error")
|
|
||||||
reject(res.description);
|
|
||||||
else if (!res.parsedFloData)
|
|
||||||
reject("Data piece (parsedFloData) missing");
|
|
||||||
else if (!res.transactionDetails)
|
|
||||||
reject("Data piece (transactionDetails) missing");
|
|
||||||
else
|
|
||||||
resolve(res);
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenAPI.sendToken = function (privKey, amount, receiverID, message = "", token = DEFAULT.currency, options = {}) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let senderID = floCrypto.getFloID(privKey);
|
|
||||||
if (typeof amount !== "number" || isNaN(amount) || amount <= 0)
|
|
||||||
return reject("Invalid amount");
|
|
||||||
getBalance(senderID, token).then(bal => {
|
|
||||||
if (amount > bal)
|
|
||||||
return reject(`Insufficient ${token}# balance`);
|
|
||||||
floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID, options)
|
|
||||||
.then(txid => resolve(txid))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendTokens_raw(privKey, receiverID, token, amount, utxo, vout, scriptPubKey) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var trx = bitjs.transaction();
|
|
||||||
trx.addinput(utxo, vout, scriptPubKey)
|
|
||||||
trx.addoutput(receiverID, floBlockchainAPI.sendAmt);
|
|
||||||
trx.addflodata(`send ${amount} ${token}#`);
|
|
||||||
var signedTxHash = trx.sign(privKey, 1);
|
|
||||||
floBlockchainAPI.broadcastTx(signedTxHash)
|
|
||||||
.then(txid => resolve([receiverID, txid]))
|
|
||||||
.catch(error => reject([receiverID, error]))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//bulk transfer tokens
|
|
||||||
tokenAPI.bulkTransferTokens = function (sender, privKey, token, receivers) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (typeof receivers !== 'object')
|
|
||||||
return reject("receivers must be object in format {receiver1: amount1, receiver2:amount2...}")
|
|
||||||
|
|
||||||
let receiver_list = Object.keys(receivers), amount_list = Object.values(receivers);
|
|
||||||
let invalidReceivers = receiver_list.filter(id => !floCrypto.validateFloID(id));
|
|
||||||
let invalidAmount = amount_list.filter(val => typeof val !== 'number' || val <= 0);
|
|
||||||
if (invalidReceivers.length)
|
|
||||||
return reject(`Invalid receivers: ${invalidReceivers}`);
|
|
||||||
else if (invalidAmount.length)
|
|
||||||
return reject(`Invalid amounts: ${invalidAmount}`);
|
|
||||||
|
|
||||||
if (receiver_list.length == 0)
|
|
||||||
return reject("Receivers cannot be empty");
|
|
||||||
|
|
||||||
if (receiver_list.length == 1) {
|
|
||||||
let receiver = receiver_list[0], amount = amount_list[0];
|
|
||||||
floTokenAPI.sendToken(privKey, amount, receiver, "", token)
|
|
||||||
.then(txid => resolve({ success: { [receiver]: txid } }))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
} else {
|
|
||||||
//check for token balance
|
|
||||||
floTokenAPI.getBalance(sender, token).then(token_balance => {
|
|
||||||
let total_token_amout = amount_list.reduce((a, e) => a + e, 0);
|
|
||||||
if (total_token_amout > token_balance)
|
|
||||||
return reject(`Insufficient ${token}# balance`);
|
|
||||||
|
|
||||||
//split utxos
|
|
||||||
floBlockchainAPI.splitUTXOs(sender, privKey, receiver_list.length).then(split_txid => {
|
|
||||||
//wait for the split utxo to get confirmation
|
|
||||||
floBlockchainAPI.waitForConfirmation(split_txid).then(split_tx => {
|
|
||||||
//send tokens using the split-utxo
|
|
||||||
var scriptPubKey = split_tx.vout[0].scriptPubKey.hex;
|
|
||||||
let promises = [];
|
|
||||||
for (let i in receiver_list)
|
|
||||||
promises.push(sendTokens_raw(privKey, receiver_list[i], token, amount_list[i], split_txid, i, scriptPubKey));
|
|
||||||
Promise.allSettled(promises).then(results => {
|
|
||||||
let success = Object.fromEntries(results.filter(r => r.status == 'fulfilled').map(r => r.value));
|
|
||||||
let failed = Object.fromEntries(results.filter(r => r.status == 'rejected').map(r => r.reason));
|
|
||||||
resolve({ success, failed });
|
|
||||||
})
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
}).catch(error => reject(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)
|
|
||||||
.then(result => resolve(result))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const util = tokenAPI.util = {};
|
|
||||||
|
|
||||||
util.parseTxData = function (txData) {
|
|
||||||
let parsedData = {};
|
|
||||||
for (let p in txData.parsedFloData)
|
|
||||||
parsedData[p] = txData.parsedFloData[p];
|
|
||||||
parsedData.sender = txData.transactionDetails.vin[0].addr;
|
|
||||||
for (let vout of txData.transactionDetails.vout)
|
|
||||||
if (vout.scriptPubKey.addresses[0] !== parsedData.sender)
|
|
||||||
parsedData.receiver = vout.scriptPubKey.addresses[0];
|
|
||||||
parsedData.time = txData.transactionDetails.time;
|
|
||||||
return parsedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
})('object' === typeof module ? module.exports : window.floTokenAPI = {});
|
|
||||||
57
index.html
57
index.html
@ -1,57 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>FLO Standard Operators</title>
|
|
||||||
<script id="floGlobals">
|
|
||||||
/* Constants for FLO blockchain operations !!Make sure to add this at beginning!! */
|
|
||||||
const floGlobals = {
|
|
||||||
blockchain: "FLO_TEST",
|
|
||||||
adminID: "oKKHdK5uYAJ52U91sYsWhnEaEAAhZP779B",
|
|
||||||
application: "TEST_MODE_testnet",
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
|
||||||
const params = Object.fromEntries(urlSearchParams.entries());
|
|
||||||
if ('testnet' in params) {
|
|
||||||
floGlobals.blockchain = "FLO_TEST";
|
|
||||||
floGlobals.adminID = "oKKHdK5uYAJ52U91sYsWhnEaEAAhZP779B";
|
|
||||||
floGlobals.application = "TEST_MODE_testnet";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('quick' in params)
|
|
||||||
window.quick = true;
|
|
||||||
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<script src="lib.js"></script>
|
|
||||||
<script src="floCrypto.js"></script>
|
|
||||||
<script src="btcOperator.js"></script>
|
|
||||||
<script src="floBlockchainAPI.js"></script>
|
|
||||||
<script src="floTokenAPI.js"></script>
|
|
||||||
<script src="compactIDB.js"></script>
|
|
||||||
<script src="floCloudAPI.js"></script>
|
|
||||||
<script src="floDapps.js"></script>
|
|
||||||
<script id="onLoadStartUp">
|
|
||||||
function onLoadStartUp() {
|
|
||||||
if (window.quick) return;
|
|
||||||
//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>
|
|
||||||
9126
standard_Operations.js
Normal file
9126
standard_Operations.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user