fix: lint (#68)
* refactor: support private methods and attributes * Adding eslint and prettier config * fix: lint files * fix: promise then/catch * fix: cb
This commit is contained in:
parent
e3ce6d87e8
commit
2b70d0b260
|
@ -0,0 +1,2 @@
|
|||
dist/
|
||||
node_modules/
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"jest": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": ["standard", "babel"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"arrow-parens": ["error", "as-needed"],
|
||||
"standard/object-curly-even-spacing": [2, "never"],
|
||||
"standard/array-bracket-even-spacing": [2, "either"],
|
||||
"standard/computed-property-even-spacing": [2, "even"],
|
||||
"standard/no-callback-literal": [2, ["cb", "callback"]],
|
||||
"babel/new-cap": 1,
|
||||
"babel/camelcase": 1,
|
||||
"babel/no-invalid-this": 1,
|
||||
"babel/object-curly-spacing": 0,
|
||||
"babel/quotes": 1,
|
||||
"babel/semi": 1,
|
||||
"babel/no-unused-expressions": 1,
|
||||
"babel/valid-typeof": 1,
|
||||
"comma-dangle": "off",
|
||||
"object-curly-newline": "off",
|
||||
"quotes": "off",
|
||||
"semi": "off",
|
||||
"object-curly-spacing": "off",
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"max-len": ["error", { "code": 120} ],
|
||||
"no-undef": "off"
|
||||
},
|
||||
"overrides": []
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"useTabs": false,
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSpacing": false,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 120
|
||||
}
|
|
@ -22,7 +22,8 @@ module.exports = api => {
|
|||
corejs: 3
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-private-methods"
|
||||
]
|
||||
},
|
||||
browser: {
|
||||
|
@ -46,7 +47,8 @@ module.exports = api => {
|
|||
useESModules: true
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-private-methods"
|
||||
]
|
||||
},
|
||||
module: {
|
||||
|
@ -70,7 +72,8 @@ module.exports = api => {
|
|||
useESModules: true
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-private-methods"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"browser": "./lib/index.js",
|
||||
"module": "./module/index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint 'src/**/*.js'",
|
||||
"clean": "rimraf dist; rimraf lib; rimraf module; rimraf react;",
|
||||
"build:browser": "cross-env BABEL_ENV=browser babel ./src --out-dir ./lib --source-maps --copy-files --ignore src/react/*",
|
||||
"build:react": "cross-env BABEL_ENV=browser babel ./src/react --out-dir ./react --source-maps --copy-files",
|
||||
|
@ -40,7 +41,13 @@
|
|||
"@babel/core": "^7.8.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.8.3",
|
||||
"@babel/plugin-proposal-private-methods": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.4",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"cross-env": "^7.0.0",
|
||||
"ganache-core": "^2.10.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
|
|
@ -1,37 +1,36 @@
|
|||
import { fromEvent } from 'rxjs';
|
||||
import loki from 'lokijs';
|
||||
import {fromEvent} from "rxjs";
|
||||
import Loki from "lokijs";
|
||||
|
||||
const getENV = function () {
|
||||
if (typeof global !== 'undefined' && (global.android || global.NSObject)) {
|
||||
// If no adapter assume nativescript which needs adapter to be passed manually
|
||||
return 'NATIVESCRIPT'; //nativescript
|
||||
const getENV = function() {
|
||||
if (typeof global !== "undefined" && (global.android || global.NSObject)) {
|
||||
// If no adapter assume nativescript which needs adapter to be passed manually
|
||||
return "NATIVESCRIPT"; //nativescript
|
||||
}
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
return 'NODEJS';
|
||||
}
|
||||
if (typeof window === "undefined") {
|
||||
return "NODEJS";
|
||||
}
|
||||
|
||||
// TODO: LokiJS determines it's running in a browser if process is undefined,
|
||||
// TODO: LokiJS determines it's running in a browser if process is undefined,
|
||||
// yet we need webpack shim for process in a different package.
|
||||
// this code ignores is the same getENV from loki except for the check for node webkit
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
if (document.URL.indexOf('http://') === -1 && document.URL.indexOf('https://') === -1) {
|
||||
return 'CORDOVA';
|
||||
if (typeof document !== "undefined") {
|
||||
if (document.URL.indexOf("http://") === -1 && document.URL.indexOf("https://") === -1) {
|
||||
return "CORDOVA";
|
||||
}
|
||||
return 'BROWSER';
|
||||
return "BROWSER";
|
||||
}
|
||||
|
||||
return 'CORDOVA';
|
||||
return "CORDOVA";
|
||||
};
|
||||
|
||||
class Database {
|
||||
|
||||
constructor(dbFilename, events, cb) {
|
||||
this.db = new loki(dbFilename, {
|
||||
this.db = new Loki(dbFilename, {
|
||||
autoload: true,
|
||||
autoloadCallback: () => {
|
||||
this.databaseInitialize()
|
||||
this.databaseInitialize();
|
||||
},
|
||||
autosave: true,
|
||||
env: getENV(),
|
||||
|
@ -42,16 +41,16 @@ class Database {
|
|||
}
|
||||
|
||||
databaseInitialize(cb) {
|
||||
let dbChanges = fromEvent(this.events, "updateDB")
|
||||
let dbChanges = fromEvent(this.events, "updateDB");
|
||||
dbChanges.subscribe(() => {
|
||||
this.db.saveDatabase()
|
||||
})
|
||||
this.db.saveDatabase();
|
||||
});
|
||||
}
|
||||
|
||||
getLastKnownEvent(eventKey) {
|
||||
const collection = this.db.getCollection(eventKey);
|
||||
if (collection && collection.count()){
|
||||
return collection.max('blockNumber');
|
||||
if (collection && collection.count()) {
|
||||
return collection.max("blockNumber");
|
||||
} else {
|
||||
this.db.addCollection(eventKey);
|
||||
}
|
||||
|
@ -60,8 +59,8 @@ class Database {
|
|||
|
||||
getFirstKnownEvent(eventKey) {
|
||||
const collection = this.db.getCollection(eventKey);
|
||||
if (collection && collection.count()){
|
||||
return collection.min('blockNumber');
|
||||
if (collection && collection.count()) {
|
||||
return collection.min("blockNumber");
|
||||
} else {
|
||||
this.db.addCollection(eventKey);
|
||||
}
|
||||
|
@ -75,12 +74,12 @@ class Database {
|
|||
|
||||
eventExists(eventKey, eventId) {
|
||||
let collection = this.db.getCollection(eventKey);
|
||||
if(!collection){
|
||||
if (!collection) {
|
||||
this.db.addCollection(eventKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
return (collection.find({ 'id': eventId }).length > 0);
|
||||
return collection.find({id: eventId}).length > 0;
|
||||
}
|
||||
|
||||
recordEvent(eventKey, values) {
|
||||
|
@ -90,18 +89,21 @@ class Database {
|
|||
|
||||
deleteEvent(eventKey, eventId) {
|
||||
const collection = this.db.getCollection(eventKey);
|
||||
if(collection)
|
||||
collection.chain().find({ 'id': eventId }).remove();
|
||||
if (collection)
|
||||
collection.chain()
|
||||
.find({id: eventId})
|
||||
.remove();
|
||||
}
|
||||
|
||||
deleteNewestBlocks(eventKey, gteBlockNum) {
|
||||
if(gteBlockNum <= 0) return;
|
||||
|
||||
const collection = this.db.getCollection(eventKey);
|
||||
if(collection)
|
||||
collection.chain().find({ 'blockNumber': {'$gte': gteBlockNum}}).remove();
|
||||
}
|
||||
if (gteBlockNum <= 0) return;
|
||||
|
||||
const collection = this.db.getCollection(eventKey);
|
||||
if (collection)
|
||||
collection.chain()
|
||||
.find({blockNumber: {$gte: gteBlockNum}})
|
||||
.remove();
|
||||
}
|
||||
}
|
||||
|
||||
export default Database;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
class NullDatabase {
|
||||
|
||||
constructor(_dbFilename, events, cb) {
|
||||
this.events = events;
|
||||
if (cb) {
|
||||
|
@ -16,7 +15,7 @@ class NullDatabase {
|
|||
|
||||
getLastKnownEvent() {
|
||||
return {
|
||||
firstKnownBlock: 0,
|
||||
firstKnownBlock: 0,
|
||||
lastKnownBlock: 0
|
||||
};
|
||||
}
|
||||
|
@ -29,15 +28,11 @@ class NullDatabase {
|
|||
return false;
|
||||
}
|
||||
|
||||
recordEvent(eventKey, values) {
|
||||
}
|
||||
recordEvent(eventKey, values) {}
|
||||
|
||||
deleteEvent(eventKey, eventId) {
|
||||
}
|
||||
|
||||
deleteNewestBlocks(eventKey, gteBlockNum) {
|
||||
}
|
||||
deleteEvent(eventKey, eventId) {}
|
||||
|
||||
deleteNewestBlocks(eventKey, gteBlockNum) {}
|
||||
}
|
||||
|
||||
export default NullDatabase;
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { fromEvent, ReplaySubject } from 'rxjs';
|
||||
import hash from 'object-hash';
|
||||
import HttpEventScanner from './httpEventScanner';
|
||||
import WsEventScanner from './wsEventScanner';
|
||||
import {fromEvent, ReplaySubject} from "rxjs";
|
||||
import hash from "object-hash";
|
||||
import HttpEventScanner from "./httpEventScanner";
|
||||
import WsEventScanner from "./wsEventScanner";
|
||||
|
||||
class EventSyncer {
|
||||
|
||||
constructor(web3, events, db, isWebsocketProvider) {
|
||||
this.events = events;
|
||||
this.web3 = web3;
|
||||
|
@ -14,7 +13,7 @@ class EventSyncer {
|
|||
}
|
||||
|
||||
track(contractInstance, eventName, filters, gteBlockNum, networkId) {
|
||||
const eventKey = hash(Object.assign({address: contractInstance.options.address, networkId}, (filters || {})));
|
||||
const eventKey = hash(Object.assign({address: contractInstance.options.address, networkId}, filters || {}));
|
||||
|
||||
this.db.deleteNewestBlocks(eventKey, gteBlockNum);
|
||||
|
||||
|
@ -22,32 +21,36 @@ class EventSyncer {
|
|||
let lastKnownBlock = this.db.getLastKnownEvent(eventKey);
|
||||
let firstKnownBlock = this.db.getFirstKnownEvent(eventKey);
|
||||
|
||||
|
||||
let sub = new ReplaySubject();
|
||||
let contractObserver = fromEvent(this.events, eventKey)
|
||||
let contractObserver = fromEvent(this.events, eventKey);
|
||||
|
||||
contractObserver.subscribe((e) => {
|
||||
contractObserver.subscribe(e => {
|
||||
if (!e) {
|
||||
sub.complete();
|
||||
return;
|
||||
}
|
||||
|
||||
const id = hash({eventName, blockNumber: e.blockNumber, transactionIndex: e.transactionIndex, logIndex: e.logIndex});
|
||||
const id = hash({
|
||||
eventName,
|
||||
blockNumber: e.blockNumber,
|
||||
transactionIndex: e.transactionIndex,
|
||||
logIndex: e.logIndex
|
||||
});
|
||||
|
||||
// TODO: would be nice if this was smart enough to understand the type of returnValues and do the needed conversions
|
||||
const eventData = {
|
||||
id,
|
||||
returnValues: {...e.returnValues},
|
||||
blockNumber: e.blockNumber,
|
||||
transactionIndex: e.transactionIndex,
|
||||
blockNumber: e.blockNumber,
|
||||
transactionIndex: e.transactionIndex,
|
||||
logIndex: e.logIndex,
|
||||
removed: e.removed
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: test reorgs
|
||||
sub.next({blockNumber: e.blockNumber, ...e.returnValues});
|
||||
sub.next({blockNumber: e.blockNumber, ...e.returnValues});
|
||||
|
||||
if (e.removed){
|
||||
if (e.removed) {
|
||||
this.db.deleteEvent(eventKey, id);
|
||||
return;
|
||||
}
|
||||
|
@ -59,26 +62,33 @@ class EventSyncer {
|
|||
this.events.emit("updateDB");
|
||||
});
|
||||
|
||||
|
||||
const fnDBEvents = this.serveDBEvents(eventKey);
|
||||
const fnPastEvents = this.getPastEvents(eventKey, contractInstance, eventName, filters);
|
||||
|
||||
if(this.isWebsocketProvider){
|
||||
if (this.isWebsocketProvider) {
|
||||
const fnSubscribe = this.subscribeToEvent(eventKey, contractInstance, eventName);
|
||||
const eth_subscribe = this.eventScanner.scan(fnDBEvents, fnPastEvents, fnSubscribe, firstKnownBlock, lastKnownBlock, filterConditions);
|
||||
const eth_subscribe = this.eventScanner.scan(
|
||||
fnDBEvents,
|
||||
fnPastEvents,
|
||||
fnSubscribe,
|
||||
firstKnownBlock,
|
||||
lastKnownBlock,
|
||||
filterConditions
|
||||
);
|
||||
|
||||
const og_subscribe = sub.subscribe;
|
||||
sub.subscribe = async (next, error, complete) => {
|
||||
const s = og_subscribe.apply(sub, [next, error, complete]);
|
||||
s.add(() => { // Removing web3js subscription when rxJS unsubscribe is executed
|
||||
s.add(() => {
|
||||
// Removing web3js subscription when rxJS unsubscribe is executed
|
||||
eth_subscribe.then(susc => {
|
||||
if(susc) {
|
||||
if (susc) {
|
||||
susc.unsubscribe();
|
||||
}
|
||||
});
|
||||
});
|
||||
return s;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
this.eventScanner.scan(fnDBEvents, fnPastEvents, lastKnownBlock, filterConditions);
|
||||
}
|
||||
|
@ -86,31 +96,31 @@ class EventSyncer {
|
|||
return sub;
|
||||
}
|
||||
|
||||
getPastEvents = (eventKey, contractInstance, eventName, filters) => async (fromBlock, toBlock, hardLimit) => {
|
||||
let events = await contractInstance.getPastEvents(eventName, { ...filters, fromBlock, toBlock });
|
||||
getPastEvents = (eventKey, contractInstance, eventName, filters) => async (fromBlock, toBlock, hardLimit) => {
|
||||
let events = await contractInstance.getPastEvents(eventName, {...filters, fromBlock, toBlock});
|
||||
const cb = this.callbackFactory(filters, eventKey);
|
||||
|
||||
|
||||
events.forEach(ev => cb(null, ev));
|
||||
|
||||
if(hardLimit && toBlock === hardLimit){ // Complete the observable
|
||||
if (hardLimit && toBlock === hardLimit) {
|
||||
// Complete the observable
|
||||
this.events.emit(eventKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
serveDBEvents = eventKey => (filters, toBlock, fromBlock = null) => {
|
||||
const cb = this.callbackFactory(filters, eventKey);
|
||||
const storedEvents = this.db.getEventsFor(eventKey).filter(x => x.blockNumber >= (fromBlock || filters.fromBlock) && x.blockNumber <= toBlock);
|
||||
storedEvents.forEach(ev => {
|
||||
cb(null, ev);
|
||||
});
|
||||
}
|
||||
this.db.getEventsFor(eventKey)
|
||||
.filter(x => x.blockNumber >= (fromBlock || filters.fromBlock) && x.blockNumber <= toBlock)
|
||||
.forEach(ev => cb(null, ev));
|
||||
};
|
||||
|
||||
subscribeToEvent = (eventKey, contractInstance, eventName) => (subscriptions, filters) => {
|
||||
const cb = this.callbackFactory(filters, eventKey);
|
||||
const s = contractInstance.events[eventName](filters, cb);
|
||||
subscriptions.push(s);
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
callbackFactory = (filterConditions, eventKey) => (err, ev) => {
|
||||
if (err) {
|
||||
|
@ -131,9 +141,9 @@ class EventSyncer {
|
|||
}
|
||||
|
||||
this.events.emit(eventKey, ev);
|
||||
}
|
||||
};
|
||||
|
||||
close(){
|
||||
close() {
|
||||
this.eventScanner.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import {sleep} from './utils'
|
||||
import {sleep} from "./utils";
|
||||
|
||||
class HttpEventScanner {
|
||||
constructor(web3){
|
||||
constructor(web3) {
|
||||
this.pollExecution = [];
|
||||
this.web3 = web3;
|
||||
}
|
||||
|
||||
async poll(execId, fn, timeout){
|
||||
async poll(execId, fn, timeout) {
|
||||
const shouldStop = await fn();
|
||||
if(!this.pollExecution[execId] || shouldStop) return;
|
||||
if(timeout) await sleep(timeout * 1000);
|
||||
if (!this.pollExecution[execId] || shouldStop) return;
|
||||
if (timeout) await sleep(timeout * 1000);
|
||||
await this.poll(execId, fn, timeout);
|
||||
}
|
||||
|
||||
|
@ -20,60 +20,68 @@ class HttpEventScanner {
|
|||
|
||||
// If there's a toBlock with a number
|
||||
let toBlockFilter = 0;
|
||||
if(filterConditions.toBlock && filterConditions.toBlock !== 'latest' ){
|
||||
if (filterConditions.toBlock && filterConditions.toBlock !== "latest") {
|
||||
toBlockFilter = filterConditions.toBlock;
|
||||
}
|
||||
const toBlockInPast = toBlockFilter && toBlockFilter < lastBlockNumberAtLoad;
|
||||
const toBlockInPast = toBlockFilter && toBlockFilter < lastBlockNumberAtLoad;
|
||||
|
||||
// Determine if data already exists and return it.
|
||||
let dbLimit = toBlockFilter > 0 ? Math.min(toBlockFilter, lastCachedBlock) : lastCachedBlock;
|
||||
if(lastCachedBlock > 0 && filterConditions.fromBlock >= 0){
|
||||
if (lastCachedBlock > 0 && filterConditions.fromBlock >= 0) {
|
||||
serveDBEvents(filterConditions, dbLimit);
|
||||
lastCachedBlock = lastCachedBlock + 1;
|
||||
}
|
||||
|
||||
lastCachedBlock = Math.max(lastCachedBlock, filterConditions.fromBlock||0);
|
||||
|
||||
lastCachedBlock = Math.max(lastCachedBlock, filterConditions.fromBlock || 0);
|
||||
|
||||
// Get old events and store them in db
|
||||
await this.poll(execId, async () => {
|
||||
try {
|
||||
const maxBlock = Math.min(lastCachedBlock + maxBlockRange, lastBlockNumberAtLoad);
|
||||
const toBlock = toBlockInPast ? Math.min(maxBlock, toBlockFilter) : maxBlock;
|
||||
const toBlockLimit = Math.min(await this.web3.getBlockNumber(), toBlock);
|
||||
const toBlockLimit = Math.min(await this.web3.getBlockNumber(), toBlock);
|
||||
|
||||
if(toBlockLimit >= lastCachedBlock) {
|
||||
await getPastEvents(lastCachedBlock, toBlockLimit, toBlockInPast ? toBlockFilter : null);
|
||||
lastCachedBlock = toBlockLimit + 1;
|
||||
if (toBlockLimit >= lastCachedBlock) {
|
||||
await getPastEvents(lastCachedBlock, toBlockLimit, toBlockInPast ? toBlockFilter : null);
|
||||
lastCachedBlock = toBlockLimit + 1;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e.toString());
|
||||
}
|
||||
|
||||
// Should exit?
|
||||
return (toBlockInPast && lastCachedBlock >= (toBlockFilter || 0)) || (lastCachedBlock > Math.max(lastBlockNumberAtLoad, toBlockInPast ? toBlockFilter || 0 : 0));
|
||||
});
|
||||
return (
|
||||
(toBlockInPast && lastCachedBlock >= (toBlockFilter || 0)) ||
|
||||
lastCachedBlock > Math.max(lastBlockNumberAtLoad, toBlockInPast ? toBlockFilter || 0 : 0)
|
||||
);
|
||||
});
|
||||
|
||||
if(toBlockInPast) return;
|
||||
if (toBlockInPast) return;
|
||||
|
||||
// Get new data, with a timeout between requests
|
||||
await this.poll(execId, async () => {
|
||||
try {
|
||||
let toBlockLimit = await this.web3.getBlockNumber()
|
||||
if(toBlockLimit >= lastCachedBlock) {
|
||||
await getPastEvents(lastCachedBlock, toBlockLimit, toBlockFilter || 0);
|
||||
lastCachedBlock = toBlockLimit + 1;
|
||||
await this.poll(
|
||||
execId,
|
||||
async () => {
|
||||
try {
|
||||
let toBlockLimit = await this.web3.getBlockNumber();
|
||||
if (toBlockLimit >= lastCachedBlock) {
|
||||
await getPastEvents(lastCachedBlock, toBlockLimit, toBlockFilter || 0);
|
||||
lastCachedBlock = toBlockLimit + 1;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e.toString());
|
||||
}
|
||||
|
||||
// Should exit?
|
||||
return filterConditions.toBlock !== 'latest' && lastCachedBlock > Math.max(lastBlockNumberAtLoad, toBlockFilter || 0);
|
||||
}, 1);
|
||||
|
||||
// Should exit?
|
||||
return (
|
||||
filterConditions.toBlock !== "latest" && lastCachedBlock > Math.max(lastBlockNumberAtLoad, toBlockFilter || 0)
|
||||
);
|
||||
},
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
close(){
|
||||
close() {
|
||||
this.pollExecution = Array(this.pollExecution.length).fill(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export {default} from './subspace';
|
||||
export * from './operators';
|
||||
export {default} from "./subspace";
|
||||
export * from "./operators";
|
||||
|
|
113
src/logSyncer.js
113
src/logSyncer.js
|
@ -1,5 +1,5 @@
|
|||
import { fromEvent, ReplaySubject } from 'rxjs';
|
||||
import hash from 'object-hash';
|
||||
import {fromEvent, ReplaySubject} from "rxjs";
|
||||
import hash from "object-hash";
|
||||
|
||||
class LogSyncer {
|
||||
constructor(web3, events, db) {
|
||||
|
@ -10,33 +10,43 @@ class LogSyncer {
|
|||
this.subscriptions = [];
|
||||
}
|
||||
|
||||
track(options, inputsABI, gteBlockNum, networkId){
|
||||
const eventKey = 'logs-' + hash(Object.assign({networkId}, options || {}));
|
||||
track(options, inputsABI, gteBlockNum, networkId) {
|
||||
const eventKey = "logs-" + hash(Object.assign({networkId}, options || {}));
|
||||
const filterConditions = Object.assign({fromBlock: 0, toBlock: "latest"}, options || {});
|
||||
|
||||
this.db.deleteNewestBlocks(eventKey, gteBlockNum);
|
||||
|
||||
const eventSummary = this.db.getLastKnownEvent(eventKey);
|
||||
const sub = new ReplaySubject();
|
||||
const logObserver = fromEvent(this.events, eventKey)
|
||||
const logObserver = fromEvent(this.events, eventKey);
|
||||
|
||||
logObserver.subscribe((e) => {
|
||||
logObserver.subscribe(e => {
|
||||
if (!e) return;
|
||||
|
||||
const id = hash({eventName: eventKey, blockNumber: e.blockNumber, transactionIndex: e.transactionIndex, logIndex: e.logIndex});
|
||||
const id = hash({
|
||||
eventName: eventKey,
|
||||
blockNumber: e.blockNumber,
|
||||
transactionIndex: e.transactionIndex,
|
||||
logIndex: e.logIndex
|
||||
});
|
||||
|
||||
// TODO: would be nice if this was smart enough to understand the type of returnValues and do the needed conversions
|
||||
const eventData = {
|
||||
id: hash({eventName: eventKey, blockNumber: e.blockNumber, transactionIndex: e.transactionIndex, logIndex: e.logIndex}),
|
||||
id: hash({
|
||||
eventName: eventKey,
|
||||
blockNumber: e.blockNumber,
|
||||
transactionIndex: e.transactionIndex,
|
||||
logIndex: e.logIndex
|
||||
}),
|
||||
data: e.data,
|
||||
address: e.address,
|
||||
topics: e.topics,
|
||||
removed: e.removed
|
||||
}
|
||||
};
|
||||
|
||||
const obsData = {blockNumber: e.blockNumber, data: e.data, address: e.address, topics: e.topics};
|
||||
|
||||
if(inputsABI){
|
||||
if (inputsABI) {
|
||||
eventData.returnValues = web3.eth.abi.decodeLog(inputsABI, e.data, e.topics.slice(1));
|
||||
obsData.returnValues = eventData.returnValues;
|
||||
}
|
||||
|
@ -45,7 +55,7 @@ class LogSyncer {
|
|||
|
||||
sub.next(obsData);
|
||||
|
||||
if(e.removed){
|
||||
if (e.removed) {
|
||||
this.db.deleteEvent(eventKey, id);
|
||||
return;
|
||||
}
|
||||
|
@ -57,20 +67,22 @@ class LogSyncer {
|
|||
this.events.emit("updateDB");
|
||||
});
|
||||
|
||||
const eth_subscribe = this._retrieveEvents(eventKey,
|
||||
eventSummary.firstKnownBlock,
|
||||
eventSummary.lastKnownBlock,
|
||||
filterConditions
|
||||
);
|
||||
const eth_subscribe = this._retrieveEvents(
|
||||
eventKey,
|
||||
eventSummary.firstKnownBlock,
|
||||
eventSummary.lastKnownBlock,
|
||||
filterConditions
|
||||
);
|
||||
|
||||
const og_subscribe = sub.subscribe;
|
||||
sub.subscribe = (next, error, complete) => {
|
||||
const s = og_subscribe.apply(sub, [next, error, complete]);
|
||||
s.add(() => { // Removing web3js subscription when rxJS unsubscribe is executed
|
||||
s.add(() => {
|
||||
// Removing web3js subscription when rxJS unsubscribe is executed
|
||||
if (eth_subscribe) eth_subscribe.unsubscribe();
|
||||
});
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
return sub;
|
||||
}
|
||||
|
@ -79,46 +91,48 @@ class LogSyncer {
|
|||
// TODO: this should be moved to a 'smart' module
|
||||
// it should be able to do events X at the time to avoid slow downs as well as the 10k limit
|
||||
if (firstKnownBlock == 0 || (firstKnownBlock > 0 && firstKnownBlock <= filterConditions.fromBlock)) {
|
||||
if (filterConditions.toBlock === 'latest') {
|
||||
if (filterConditions.toBlock === "latest") {
|
||||
// emit DB Events [fromBlock, lastKnownBlock]
|
||||
this._serveDBEvents(eventKey, filterConditions.fromBlock, lastKnownBlock, filterConditions);
|
||||
// create a event subscription [lastKnownBlock + 1, ...]
|
||||
let filters = Object.assign({}, filterConditions, { fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1 });
|
||||
let filters = Object.assign({}, filterConditions, {
|
||||
fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1
|
||||
});
|
||||
return this._subscribeToEvent(filters, eventKey);
|
||||
}
|
||||
else if (filterConditions.toBlock <= lastKnownBlock) {
|
||||
} else if (filterConditions.toBlock <= lastKnownBlock) {
|
||||
// emit DB Events [fromBlock, toBlock]
|
||||
this._serveDBEvents(eventKey, filterConditions.fromBlock, filterConditions.toBlock, filterConditions);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// emit DB Events [fromBlock, lastKnownBlock]
|
||||
this._serveDBEvents(eventKey, filterConditions.fromBlock, lastKnownBlock, filterConditions);
|
||||
// create a past event subscription [lastKnownBlock + 1, toBlock]
|
||||
let filters = Object.assign({}, filterConditions, { fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1 });
|
||||
let filters = Object.assign({}, filterConditions, {
|
||||
fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1
|
||||
});
|
||||
this._getPastEvents(filters, eventKey);
|
||||
}
|
||||
}
|
||||
else if (firstKnownBlock > 0) {
|
||||
} else if (firstKnownBlock > 0) {
|
||||
// create a past event subscription [ firstKnownBlock > fromBlock ? fromBlock : 0, firstKnownBlock - 1]
|
||||
let fromBlock = firstKnownBlock > filterConditions.fromBlock ? filterConditions.fromBlock : 0;
|
||||
let filters = Object.assign({}, filterConditions, { fromBlock, toBlock: firstKnownBlock - 1 });
|
||||
let filters = Object.assign({}, filterConditions, {fromBlock, toBlock: firstKnownBlock - 1});
|
||||
this._getPastEvents(filters, eventKey);
|
||||
if (filterConditions.toBlock === 'latest') {
|
||||
if (filterConditions.toBlock === "latest") {
|
||||
// emit DB Events [firstKnownBlock, lastKnownBlock]
|
||||
this._serveDBEvents(eventKey, firstKnownBlock, lastKnownBlock, filterConditions);
|
||||
// create a subscription [lastKnownBlock + 1, ...]
|
||||
const filters = Object.assign({}, filterConditions, { fromBlock: lastKnownBlock + 1 });
|
||||
const filters = Object.assign({}, filterConditions, {fromBlock: lastKnownBlock + 1});
|
||||
return this._subscribeToEvent(filters, eventKey);
|
||||
}
|
||||
else if (filterConditions.toBlock <= lastKnownBlock) {
|
||||
} else if (filterConditions.toBlock <= lastKnownBlock) {
|
||||
// emit DB Events [fromBlock, toBlock]
|
||||
this._serveDBEvents(eventKey, filterConditions.fromBlock, filterConditions.toBlock, filterConditions);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// emit DB Events [fromBlock, lastKnownBlock]
|
||||
this._serveDBEvents(eventKey, filterConditions.fromBlock, lastKnownBlock, filterConditions);
|
||||
// create a past event subscription [lastKnownBlock + 1, toBlock]
|
||||
let filters = Object.assign({}, filterConditions, { fromBlock: lastKnownBlock + 1, toBlock: filterConditions.toBlock });
|
||||
let filters = Object.assign({}, filterConditions, {
|
||||
fromBlock: lastKnownBlock + 1,
|
||||
toBlock: filterConditions.toBlock
|
||||
});
|
||||
this._getPastEvents(filters, eventKey);
|
||||
}
|
||||
}
|
||||
|
@ -126,27 +140,24 @@ class LogSyncer {
|
|||
|
||||
_serveDBEvents(eventKey, firstKnownBlock, lastKnownBlock, filterConditions) {
|
||||
const cb = this._parseEventCBFactory(filterConditions, eventKey);
|
||||
const storedEvents = this.db.getEventsFor(eventKey).filter(x => x.blockNumber >= firstKnownBlock && x.blockNumber <= lastKnownBlock);
|
||||
storedEvents.forEach(ev => {
|
||||
cb(null, ev);
|
||||
});
|
||||
this.db.getEventsFor(eventKey)
|
||||
.filter(x => x.blockNumber >= firstKnownBlock && x.blockNumber <= lastKnownBlock)
|
||||
.forEach(ev => cb(null, ev));
|
||||
}
|
||||
|
||||
_getPastEvents(filterConditions, eventKey) {
|
||||
const cb = this._parseEventCBFactory(filterConditions, eventKey);
|
||||
this.web3.getPastLogs(options, (err, logs) => {
|
||||
if(err) {
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
logs.forEach(l => {
|
||||
cb(null, l);
|
||||
})
|
||||
logs.forEach(l => cb(null, l));
|
||||
});
|
||||
}
|
||||
|
||||
_subscribeToEvent(filterConditions, eventKey) {
|
||||
const s = this.web3.subscribe('logs', filterConditions, this._parseEventCBFactory(filterConditions, eventKey));
|
||||
const s = this.web3.subscribe("logs", filterConditions, this._parseEventCBFactory(filterConditions, eventKey));
|
||||
this.subscriptions.push(s);
|
||||
return s;
|
||||
}
|
||||
|
@ -158,24 +169,22 @@ class LogSyncer {
|
|||
|
||||
if (filterConditions) {
|
||||
if (filterConditions.address && ev.address.toLowerCase() !== filterConditions.address.toLowerCase()) return;
|
||||
if (filterConditions.topics){
|
||||
if (filterConditions.topics) {
|
||||
let shouldSkip = false;
|
||||
filterConditions.topics.forEach((topic, i) => {
|
||||
if (topic != null && (!ev.topics[i] || ev.topics[i].toLowerCase() !== topic.toLowerCase())){
|
||||
if (topic != null && (!ev.topics[i] || ev.topics[i].toLowerCase() !== topic.toLowerCase())) {
|
||||
shouldSkip = true;
|
||||
}
|
||||
});
|
||||
if(shouldSkip) return;
|
||||
if (shouldSkip) return;
|
||||
}
|
||||
}
|
||||
|
||||
this.events.emit(eventKey, ev);
|
||||
}
|
||||
};
|
||||
|
||||
close(){
|
||||
this.subscriptions.forEach(x => {
|
||||
x.unsubscribe();
|
||||
})
|
||||
close() {
|
||||
this.subscriptions.forEach(x => x.unsubscribe());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { of, pipe } from 'rxjs';
|
||||
import { map, pluck, scan } from 'rxjs/operators';
|
||||
import {pipe} from "rxjs";
|
||||
import {map, scan} from "rxjs/operators";
|
||||
|
||||
export function $takeProps() {
|
||||
const args = Object.values(arguments);
|
||||
|
@ -10,7 +10,7 @@ export function $takeProps() {
|
|||
r[a] = v[a];
|
||||
});
|
||||
return r;
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -22,33 +22,36 @@ of({a: 1, b:2, e: 1}, {a: 0, c: 1, b:3}, {a: 0, d: 1, b:1})
|
|||
*/
|
||||
|
||||
export function $average(cb) {
|
||||
return pipe(
|
||||
scan((accum, curr) => {
|
||||
return pipe(
|
||||
scan(
|
||||
(accum, curr) => {
|
||||
let currentValue;
|
||||
if (typeof cb === 'string' || cb instanceof String){
|
||||
if (typeof cb === "string" || cb instanceof String) {
|
||||
currentValue = curr[cb];
|
||||
} else if(typeof cb === "function") {
|
||||
} else if (typeof cb === "function") {
|
||||
currentValue = cb(curr);
|
||||
} else {
|
||||
currentValue = curr;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
sum: accum.sum + currentValue,
|
||||
count: accum.count + 1
|
||||
}
|
||||
}, { sum: 0, count: 0 }),
|
||||
map(o => o.sum / o.count)
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
{sum: 0, count: 0}
|
||||
),
|
||||
map(o => o.sum / o.count)
|
||||
);
|
||||
}
|
||||
|
||||
export function $max(cb) {
|
||||
return pipe(
|
||||
scan((acc, curr) => {
|
||||
let currentValue;
|
||||
if (typeof cb === 'string' || cb instanceof String){
|
||||
if (typeof cb === "string" || cb instanceof String) {
|
||||
currentValue = curr[cb];
|
||||
} else if(typeof cb === "function") {
|
||||
} else if (typeof cb === "function") {
|
||||
currentValue = cb(curr);
|
||||
} else {
|
||||
currentValue = curr;
|
||||
|
@ -64,9 +67,9 @@ export function $min(cb) {
|
|||
return pipe(
|
||||
scan((acc, curr) => {
|
||||
let currentValue;
|
||||
if (typeof cb === 'string' || cb instanceof String){
|
||||
if (typeof cb === "string" || cb instanceof String) {
|
||||
currentValue = curr[cb];
|
||||
} else if(typeof cb === "function") {
|
||||
} else if (typeof cb === "function") {
|
||||
currentValue = cb(curr);
|
||||
} else {
|
||||
currentValue = curr;
|
||||
|
@ -83,9 +86,9 @@ export function $latest(num) {
|
|||
scan((acc, curr) => {
|
||||
let currentValue = curr;
|
||||
|
||||
acc.push(currentValue)
|
||||
acc.push(currentValue);
|
||||
if (acc.length > num) {
|
||||
acc.shift()
|
||||
acc.shift();
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { Component } from "react";
|
||||
import { isObservable } from "rxjs";
|
||||
import React, {Component} from "react";
|
||||
import {isObservable} from "rxjs";
|
||||
|
||||
export function observe(WrappedComponent) {
|
||||
return class extends Component {
|
||||
|
@ -9,11 +9,11 @@ export function observe(WrappedComponent) {
|
|||
};
|
||||
|
||||
unsubscribe = prop => {
|
||||
const subscriptions = { ...this.state.subscriptions };
|
||||
const subscriptions = {...this.state.subscriptions};
|
||||
if (subscriptions[prop]) subscriptions[prop].unsubscribe();
|
||||
delete subscriptions[prop];
|
||||
|
||||
this.setState({ subscriptions });
|
||||
this.setState({subscriptions});
|
||||
};
|
||||
|
||||
subscribeToProp = prop => {
|
||||
|
|
325
src/subspace.js
325
src/subspace.js
|
@ -1,18 +1,27 @@
|
|||
import { ReplaySubject, BehaviorSubject } from 'rxjs';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import equal from 'fast-deep-equal';
|
||||
import Database from './database/database.js';
|
||||
import NullDatabase from './database/nullDatabase.js';
|
||||
import Events from 'events';
|
||||
import Web3Eth from 'web3-eth';
|
||||
import {isAddress} from './utils';
|
||||
import stripHexPrefix from 'strip-hex-prefix';
|
||||
import {hexToDec} from 'hex2dec';
|
||||
import EventSyncer from './eventSyncer';
|
||||
import LogSyncer from './logSyncer';
|
||||
import hash from 'object-hash';
|
||||
import {ReplaySubject, BehaviorSubject} from "rxjs";
|
||||
import {distinctUntilChanged, map} from "rxjs/operators";
|
||||
import equal from "fast-deep-equal";
|
||||
import Database from "./database/database.js";
|
||||
import NullDatabase from "./database/nullDatabase.js";
|
||||
import Events from "events";
|
||||
import Web3Eth from "web3-eth";
|
||||
import {isAddress} from "./utils";
|
||||
import stripHexPrefix from "strip-hex-prefix";
|
||||
import {hexToDec} from "hex2dec";
|
||||
import EventSyncer from "./eventSyncer";
|
||||
import LogSyncer from "./logSyncer";
|
||||
import hash from "object-hash";
|
||||
|
||||
export default class Subspace {
|
||||
subjects = {};
|
||||
callables = [];
|
||||
|
||||
newBlocksSubscription = null;
|
||||
intervalTracker = null;
|
||||
|
||||
// Stats
|
||||
latestBlockNumber = undefined;
|
||||
latest10Blocks = [];
|
||||
|
||||
constructor(provider, options = {}) {
|
||||
if (!provider.on) {
|
||||
|
@ -26,25 +35,16 @@ export default class Subspace {
|
|||
this.options = {};
|
||||
this.options.refreshLastNBlocks = options.refreshLastNBlocks || 12;
|
||||
this.options.callInterval = options.callInterval || 0;
|
||||
this.options.dbFilename = options.dbFilename || 'subspace.db';
|
||||
this.disableDatabase = options.disableDatabase;
|
||||
this.options.dbFilename = options.dbFilename || "subspace.db";
|
||||
this.options.disableDatabase = options.disableDatabase;
|
||||
|
||||
this.networkId = undefined;
|
||||
this.isWebsocketProvider = options.disableSubscriptions ? false : !!provider.on;
|
||||
|
||||
// Stats
|
||||
this.latestBlockNumber = undefined;
|
||||
this.latest10Blocks = [];
|
||||
|
||||
this.subjects = {};
|
||||
|
||||
this.newBlocksSubscription = null;
|
||||
this.intervalTracker = null;
|
||||
this.callables = [];
|
||||
}
|
||||
|
||||
init() {
|
||||
return new Promise(async (resolve) => {
|
||||
if (this.disableDatabase === true) {
|
||||
return new Promise(async resolve => {
|
||||
if (this.options.disableDatabase === true) {
|
||||
this._db = new NullDatabase("", this.events);
|
||||
} else {
|
||||
this._db = new Database(this.options.dbFilename, this.events);
|
||||
|
@ -56,12 +56,12 @@ export default class Subspace {
|
|||
this.networkId = netId;
|
||||
});
|
||||
|
||||
const block = await this.web3.getBlock('latest');
|
||||
const block = await this.web3.getBlock("latest");
|
||||
|
||||
// Preload <= 10 blocks to calculate avg block time
|
||||
if(block.number !== 0){
|
||||
if (block.number !== 0) {
|
||||
const minBlock = Math.max(0, block.number - 9);
|
||||
for(let i = minBlock; i < block.number; i++){
|
||||
for (let i = minBlock; i < block.number; i++) {
|
||||
this.latest10Blocks.push(this.web3.getBlock(i));
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ export default class Subspace {
|
|||
this.latestBlockNumber = block.number;
|
||||
this.latest10Blocks.push(block);
|
||||
|
||||
if(this.isWebsocketProvider){
|
||||
if (this.isWebsocketProvider) {
|
||||
this._initNewBlocksSubscription();
|
||||
} else {
|
||||
this.options.callInterval = this.options.callInterval || 1000;
|
||||
|
@ -85,7 +85,7 @@ export default class Subspace {
|
|||
|
||||
contract(contractInstance) {
|
||||
if (!contractInstance) {
|
||||
throw new Error("please pass a contract instance to Subspace.contract()")
|
||||
throw new Error("please pass a contract instance to Subspace.contract()");
|
||||
}
|
||||
|
||||
let address = (contractInstance.options && contractInstance.options.address) || contractInstance.address || contractInstance.deployedAddress;
|
||||
|
@ -106,40 +106,40 @@ export default class Subspace {
|
|||
|
||||
SubspaceContract.trackEvent = (eventName, filterConditionsOrCb) => {
|
||||
return this.trackEvent(SubspaceContract, eventName, filterConditionsOrCb);
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(SubspaceContract.events).forEach(ev => {
|
||||
if(!SubspaceContract.options.jsonInterface.find(x => x.type === 'event' && x.name == ev)) return;
|
||||
SubspaceContract.events[ev].track = (filterConditionsOrCb) => this.trackEvent(SubspaceContract, ev, filterConditionsOrCb);
|
||||
if (!SubspaceContract.options.jsonInterface.find(x => x.type === "event" && x.name == ev)) return;
|
||||
SubspaceContract.events[ev].track = filterConditionsOrCb =>
|
||||
this.trackEvent(SubspaceContract, ev, filterConditionsOrCb);
|
||||
});
|
||||
|
||||
SubspaceContract.trackProperty = (propName, methodArgs, callArgs) => {
|
||||
return this.trackProperty(SubspaceContract, propName, methodArgs, callArgs);
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(SubspaceContract.methods).forEach(methodName => {
|
||||
const oldFunc = SubspaceContract.methods[methodName];
|
||||
|
||||
const _this = this;
|
||||
const newFunc = function(){
|
||||
const newFunc = function() {
|
||||
const txObject = oldFunc.apply(null, arguments);
|
||||
txObject.track = (callArgs) => _this.trackProperty(SubspaceContract, methodName, txObject.arguments, callArgs);
|
||||
txObject.track = callArgs => _this.trackProperty(SubspaceContract, methodName, txObject.arguments, callArgs);
|
||||
return txObject;
|
||||
}
|
||||
};
|
||||
|
||||
SubspaceContract.methods[methodName] = newFunc;
|
||||
});
|
||||
|
||||
SubspaceContract.trackBalance = (erc20Address) => {
|
||||
SubspaceContract.trackBalance = erc20Address => {
|
||||
return this.trackBalance(SubspaceContract.options.address, erc20Address);
|
||||
}
|
||||
};
|
||||
|
||||
return SubspaceContract;
|
||||
}
|
||||
|
||||
|
||||
clearDB(collection) {
|
||||
if (collection){
|
||||
if (collection) {
|
||||
// TODO: delete specific collection
|
||||
} else {
|
||||
// TODO: delete everything
|
||||
|
@ -148,16 +148,14 @@ export default class Subspace {
|
|||
|
||||
_initNewBlocksSubscription() {
|
||||
if (this.newBlocksSubscription != null || this.options.callInterval !== 0) return;
|
||||
|
||||
this.newBlocksSubscription = this.web3.subscribe('newBlockHeaders', (err, result) => {
|
||||
|
||||
this.newBlocksSubscription = this.web3.subscribe("newBlockHeaders", (err, result) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
this.callables.forEach(fn => {
|
||||
fn();
|
||||
});
|
||||
this.callables.forEach(fn => fn());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -165,21 +163,19 @@ export default class Subspace {
|
|||
if (this.intervalTracker != null || this.options.callInterval === 0) return;
|
||||
|
||||
this.intervalTracker = setInterval(() => {
|
||||
this.callables.forEach(fn => {
|
||||
fn();
|
||||
});
|
||||
this.callables.forEach(fn => fn());
|
||||
}, this.options.callInterval);
|
||||
}
|
||||
|
||||
_getSubject(subjectHash, subjectCB){
|
||||
if(this.subjects[subjectHash]) return this.subjects[subjectHash];
|
||||
_getSubject(subjectHash, subjectCB) {
|
||||
if (this.subjects[subjectHash]) return this.subjects[subjectHash];
|
||||
this.subjects[subjectHash] = subjectCB();
|
||||
return this.subjects[subjectHash];
|
||||
}
|
||||
|
||||
#_addDistinctCallable(trackAttribute, cbBuilder, subject, subjectArg = undefined) {
|
||||
_addDistinctCallable(trackAttribute, cbBuilder, SubjectType, subjectArg = undefined) {
|
||||
return this._getSubject(trackAttribute, () => {
|
||||
const sub = new subject(subjectArg);
|
||||
const sub = new SubjectType(subjectArg);
|
||||
const cb = cbBuilder(sub);
|
||||
cb();
|
||||
this.callables.push(cb);
|
||||
|
@ -188,24 +184,31 @@ export default class Subspace {
|
|||
}
|
||||
|
||||
trackEvent(contractInstance, eventName, filterConditions) {
|
||||
const subjectHash = hash({address: contractInstance.options.address, networkId: this.networkId, eventName, filterConditions});
|
||||
const subjectHash = hash({
|
||||
address: contractInstance.options.address,
|
||||
networkId: this.networkId,
|
||||
eventName,
|
||||
filterConditions
|
||||
});
|
||||
return this._getSubject(subjectHash, () => {
|
||||
let deleteFrom = this.latestBlockNumber - this.options.refreshLastNBlocks;
|
||||
let returnSub = this.eventSyncer.track(contractInstance, eventName, filterConditions, deleteFrom, this.networkId);
|
||||
|
||||
returnSub.map = (prop) => {
|
||||
return returnSub.pipe(map((x) => {
|
||||
if (typeof(prop) === "string") {
|
||||
return x[prop];
|
||||
}
|
||||
if (Array.isArray(prop)) {
|
||||
let newValues = {}
|
||||
prop.forEach((p) => {
|
||||
newValues[p] = x[p]
|
||||
})
|
||||
return newValues
|
||||
}
|
||||
}))
|
||||
|
||||
returnSub.map = prop => {
|
||||
return returnSub.pipe(
|
||||
map(x => {
|
||||
if (typeof prop === "string") {
|
||||
return x[prop];
|
||||
}
|
||||
if (Array.isArray(prop)) {
|
||||
let newValues = {};
|
||||
prop.forEach(p => {
|
||||
newValues[p] = x[p];
|
||||
});
|
||||
return newValues;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return returnSub;
|
||||
|
@ -213,128 +216,156 @@ export default class Subspace {
|
|||
}
|
||||
|
||||
trackLogs(options, inputsABI) {
|
||||
if(!this.isWebsocketProvider) console.warn("This method only works with websockets");
|
||||
if (!this.isWebsocketProvider) console.warn("This method only works with websockets");
|
||||
|
||||
const subjectHash = hash({inputsABI, options});
|
||||
return this._getSubject(subjectHash, () =>
|
||||
const subjectHash = hash({inputsABI, options});
|
||||
return this._getSubject(subjectHash, () =>
|
||||
this.logSyncer.track(options, inputsABI, this.latestBlockNumber - this.options.refreshLastNBlocks, this.networkId)
|
||||
);
|
||||
}
|
||||
|
||||
trackProperty(contractInstance, propName, methodArgs = [], callArgs = {}) {
|
||||
const subjectHash = hash({address: contractInstance.options.address, networkId: this.networkId, propName, methodArgs, callArgs});
|
||||
|
||||
const subjectHash = hash({
|
||||
address: contractInstance.options.address,
|
||||
networkId: this.networkId,
|
||||
propName,
|
||||
methodArgs,
|
||||
callArgs
|
||||
});
|
||||
|
||||
return this._getSubject(subjectHash, () => {
|
||||
const subject = new ReplaySubject(1);
|
||||
|
||||
if (!Array.isArray(methodArgs)) {
|
||||
methodArgs = [methodArgs]
|
||||
methodArgs = [methodArgs];
|
||||
}
|
||||
|
||||
|
||||
const method = contractInstance.methods[propName].apply(contractInstance.methods[propName], methodArgs);
|
||||
|
||||
|
||||
const callContractMethod = () => {
|
||||
method.call.apply(method.call, [callArgs, (err, result) => {
|
||||
if (err) {
|
||||
subject.error(err);
|
||||
return;
|
||||
method.call.apply(method.call, [
|
||||
callArgs,
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
subject.error(err);
|
||||
return;
|
||||
}
|
||||
subject.next(result);
|
||||
}
|
||||
subject.next(result);
|
||||
}]);
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
callContractMethod();
|
||||
|
||||
|
||||
this.callables.push(callContractMethod);
|
||||
|
||||
|
||||
const returnSub = subject.pipe(distinctUntilChanged((a, b) => equal(a, b)));
|
||||
|
||||
returnSub.map = (prop) => {
|
||||
return returnSub.pipe(map((x) => {
|
||||
if (typeof(prop) === "string") {
|
||||
return x[prop];
|
||||
}
|
||||
if (Array.isArray(prop)) {
|
||||
let newValues = {}
|
||||
prop.forEach((p) => {
|
||||
newValues[p] = x[p]
|
||||
})
|
||||
return newValues
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
returnSub.map = prop => {
|
||||
return returnSub.pipe(
|
||||
map(x => {
|
||||
if (typeof prop === "string") {
|
||||
return x[prop];
|
||||
}
|
||||
if (Array.isArray(prop)) {
|
||||
let newValues = {};
|
||||
prop.forEach(p => {
|
||||
newValues[p] = x[p];
|
||||
});
|
||||
return newValues;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return returnSub;
|
||||
});
|
||||
}
|
||||
|
||||
trackBalance(address, erc20Address) {
|
||||
if (!isAddress(address)) throw "invalid address"
|
||||
if (erc20Address && !isAddress(erc20Address)) throw "invalid ERC20 contract address"
|
||||
if (!isAddress(address)) throw "invalid address";
|
||||
if (erc20Address && !isAddress(erc20Address)) throw "invalid ERC20 contract address";
|
||||
|
||||
const subjectHash = hash({address, erc20Address});
|
||||
|
||||
const getETHBalance = (cb) => {
|
||||
const fn = this.web3.getBalance;
|
||||
const subjectHash = hash({address, erc20Address});
|
||||
|
||||
const getETHBalance = cb => {
|
||||
const fn = this.web3.getBalance;
|
||||
fn.apply(fn, [address, cb]);
|
||||
}
|
||||
};
|
||||
|
||||
const getTokenBalance = (cb) => {
|
||||
const fn = this.web3.call;
|
||||
// balanceOf
|
||||
const data = "0x70a08231" + "000000000000000000000000" + stripHexPrefix(address);
|
||||
const getTokenBalance = cb => {
|
||||
const fn = this.web3.call;
|
||||
// balanceOf
|
||||
const data = "0x70a08231" + "000000000000000000000000" + stripHexPrefix(address);
|
||||
fn.apply(fn, [{to: erc20Address, data}, cb]);
|
||||
}
|
||||
};
|
||||
|
||||
let callFn;
|
||||
if (!erc20Address){
|
||||
callFn = subject => () => getETHBalance((err, balance) => {
|
||||
if(err) {
|
||||
if (!erc20Address) {
|
||||
callFn = subject => () =>
|
||||
getETHBalance((err, balance) => {
|
||||
if (err) {
|
||||
subject.error(err);
|
||||
return;
|
||||
}
|
||||
subject.next(balance);
|
||||
});
|
||||
} else {
|
||||
callFn = subject => () => getTokenBalance((err, balance) => {
|
||||
if(err) {
|
||||
subject.error(err);
|
||||
return;
|
||||
}
|
||||
subject.next(balance);
|
||||
});
|
||||
callFn = subject => () =>
|
||||
getTokenBalance((err, balance) => {
|
||||
if (err) {
|
||||
subject.error(err);
|
||||
return;
|
||||
}
|
||||
subject.next(hexToDec(balance));
|
||||
});
|
||||
}
|
||||
|
||||
return this._addDistinctCallable(subjectHash, callFn, ReplaySubject, 1);
|
||||
}
|
||||
|
||||
trackBlock() {
|
||||
const blockCB = (subject) => () => {
|
||||
this.web3.getBlock('latest').then(block => {
|
||||
if(this.latest10Blocks[this.latest10Blocks.length - 1].number === block.number) return;
|
||||
const blockCB = subject => () => {
|
||||
this.web3
|
||||
.getBlock("latest")
|
||||
.then(block => {
|
||||
if (this.latest10Blocks[this.latest10Blocks.length - 1].number === block.number) return;
|
||||
|
||||
this.latest10Blocks.push(block);
|
||||
if(this.latest10Blocks.length > 10){
|
||||
this.latest10Blocks.shift();
|
||||
}
|
||||
subject.next(block);
|
||||
}).catch(error => subject.error(error));
|
||||
this.latest10Blocks.push(block);
|
||||
if (this.latest10Blocks.length > 10) {
|
||||
this.latest10Blocks.shift();
|
||||
}
|
||||
subject.next(block);
|
||||
})
|
||||
.catch(error => subject.error(error));
|
||||
};
|
||||
return this._addDistinctCallable('blockObservable', blockCB, BehaviorSubject, this.latest10Blocks[this.latest10Blocks.length - 1]);
|
||||
|
||||
return this._addDistinctCallable(
|
||||
"blockObservable",
|
||||
blockCB,
|
||||
BehaviorSubject,
|
||||
this.latest10Blocks[this.latest10Blocks.length - 1]
|
||||
);
|
||||
}
|
||||
|
||||
trackBlockNumber() {
|
||||
const blockNumberCB = (subject) => () => {
|
||||
this.web3.getBlockNumber().then(blockNumber => subject.next(blockNumber)).catch(error => subject.error(error));
|
||||
const blockNumberCB = subject => () => {
|
||||
this.web3
|
||||
.getBlockNumber()
|
||||
.then(result => subject.next(result))
|
||||
.catch(error => subject.error(error));
|
||||
};
|
||||
return this._addDistinctCallable('blockNumberObservable', blockNumberCB, ReplaySubject, 1);
|
||||
return this._addDistinctCallable("blockNumberObservable", blockNumberCB, ReplaySubject, 1);
|
||||
}
|
||||
|
||||
trackGasPrice() {
|
||||
const gasPriceCB = (subject) => () => {
|
||||
this.web3.getGasPrice().then(gasPrice => subject.next(gasPrice)).catch(error => subject.error(error));
|
||||
const gasPriceCB = subject => () => {
|
||||
this.web3
|
||||
.getGasPrice()
|
||||
.then(result => subject.next(result))
|
||||
.catch(error => subject.error(error));
|
||||
};
|
||||
return this._addDistinctCallable('gasPriceObservable', gasPriceCB, ReplaySubject, 1);
|
||||
return this._addDistinctCallable("gasPriceObservable", gasPriceCB, ReplaySubject, 1);
|
||||
}
|
||||
|
||||
trackAverageBlocktime() {
|
||||
|
@ -344,21 +375,21 @@ export default class Subspace {
|
|||
const times = [];
|
||||
for (let i = 1; i < this.latest10Blocks.length; i++) {
|
||||
let time = this.latest10Blocks[i].timestamp - this.latest10Blocks[i - 1].timestamp;
|
||||
times.push(time)
|
||||
times.push(time);
|
||||
}
|
||||
return times.length ? Math.round(times.reduce((a, b) => a + b) / times.length) * 1000 : 0;
|
||||
}
|
||||
};
|
||||
|
||||
const avgTimeCB = (subject) => () => subject.next(calcAverage());
|
||||
|
||||
return this._addDistinctCallable('blockTimeObservable', avgTimeCB, BehaviorSubject, calcAverage());
|
||||
const avgTimeCB = subject => () => subject.next(calcAverage());
|
||||
|
||||
return this._addDistinctCallable("blockTimeObservable", avgTimeCB, BehaviorSubject, calcAverage());
|
||||
}
|
||||
|
||||
close(){
|
||||
close() {
|
||||
clearInterval(this.intervalTracker);
|
||||
if (this.newBlocksSubscription) this.newBlocksSubscription.unsubscribe();
|
||||
this.eventSyncer.close();
|
||||
this.intervalTracker = null;
|
||||
this.callables = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
src/utils.js
12
src/utils.js
|
@ -1,13 +1,7 @@
|
|||
|
||||
export function randomString() {
|
||||
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
|
||||
export function isAddress(address) {
|
||||
return /^(0x)?[0-9a-fA-F]{40}$/i.test(address)
|
||||
};
|
||||
|
||||
return /^(0x)?[0-9a-fA-F]{40}$/i.test(address);
|
||||
}
|
||||
|
||||
export function sleep(milliseconds) {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,58 +9,55 @@ class WsEventScanner {
|
|||
|
||||
// If there's a toBlock with a number
|
||||
let toBlockFilter = 0;
|
||||
if(filterConditions.toBlock && filterConditions.toBlock !== 'latest' ){
|
||||
if (filterConditions.toBlock && filterConditions.toBlock !== "latest") {
|
||||
toBlockFilter = filterConditions.toBlock;
|
||||
}
|
||||
const toBlockInPast = toBlockFilter && toBlockFilter < lastBlockNumberAtLoad;
|
||||
const toBlockInPast = toBlockFilter && toBlockFilter < lastBlockNumberAtLoad;
|
||||
const hardLimit = toBlockInPast ? toBlockFilter : null;
|
||||
|
||||
|
||||
if (firstKnownBlock == 0 || (firstKnownBlock > 0 && firstKnownBlock <= filterConditions.fromBlock)) {
|
||||
if (filterConditions.toBlock === 'latest') {
|
||||
if (filterConditions.toBlock === "latest") {
|
||||
// emit DB Events [fromBlock, lastKnownBlock]
|
||||
serveDBEvents(filterConditions, lastKnownBlock);
|
||||
// create a event subscription [lastKnownBlock + 1, ...]
|
||||
let filters = Object.assign({}, filterConditions, { fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1 });
|
||||
// create a event subscription [lastKnownBlock + 1, ...]
|
||||
let filters = Object.assign({}, filterConditions, {
|
||||
fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1
|
||||
});
|
||||
return subscribe(this.subscriptions, filters);
|
||||
}
|
||||
else if (filterConditions.toBlock <= lastKnownBlock) {
|
||||
} else if (filterConditions.toBlock <= lastKnownBlock) {
|
||||
// emit DB Events [fromBlock, toBlock]
|
||||
serveDBEvents(filterConditions, filterConditions.toBlock);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// emit DB Events [fromBlock, lastKnownBlock]
|
||||
serveDBEvents(filterConditions, lastKnownBlock);
|
||||
// get past events [lastKnownBlock + 1, toBlock]
|
||||
const fromBlock = filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1;
|
||||
await getPastEvents(fromBlock, filterConditions.toBlock, hardLimit);
|
||||
await getPastEvents(fromBlock, filterConditions.toBlock, hardLimit);
|
||||
}
|
||||
}
|
||||
else if (firstKnownBlock > 0) {
|
||||
} else if (firstKnownBlock > 0) {
|
||||
// get past events [ firstKnownBlock > fromBlock ? fromBlock : 0, firstKnownBlock - 1]
|
||||
const fromBlock = firstKnownBlock > filterConditions.fromBlock ? filterConditions.fromBlock : 0;
|
||||
await getPastEvents(fromBlock, firstKnownBlock - 1, hardLimit);
|
||||
await getPastEvents(fromBlock, firstKnownBlock - 1, hardLimit);
|
||||
|
||||
if (filterConditions.toBlock === 'latest') {
|
||||
if (filterConditions.toBlock === "latest") {
|
||||
// emit DB Events [firstKnownBlock, lastKnownBlock]
|
||||
serveDBEvents(filterConditions, lastKnownBlock, firstKnownBlock);
|
||||
// create a subscription [lastKnownBlock + 1, ...]
|
||||
const filters = Object.assign({}, filterConditions, { fromBlock: lastKnownBlock + 1 });
|
||||
const filters = Object.assign({}, filterConditions, {fromBlock: lastKnownBlock + 1});
|
||||
return subscribe(this.subscriptions, filters);
|
||||
}
|
||||
else if (filterConditions.toBlock <= lastKnownBlock) {
|
||||
} else if (filterConditions.toBlock <= lastKnownBlock) {
|
||||
// emit DB Events [fromBlock, toBlock]
|
||||
serveDBEvents(filterConditions, filterConditions.toBlock);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// emit DB Events [fromBlock, lastKnownBlock]
|
||||
serveDBEvents(filterConditions, lastKnownBlock);
|
||||
// get past events [lastKnownBlock + 1, toBlock]
|
||||
await getPastEvents(lastKnownBlock + 1, filterConditions.toBlock, hardLimit);
|
||||
await getPastEvents(lastKnownBlock + 1, filterConditions.toBlock, hardLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(){
|
||||
close() {
|
||||
this.subscriptions.forEach(x => x.unsubscribe());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue