@plugnet/rpc-rx
Version:
An RxJs wrapper around the Plugnet JS API
177 lines (146 loc) • 5.53 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _eventemitter = _interopRequireDefault(require("eventemitter3"));
var _memoizee = _interopRequireDefault(require("memoizee"));
var _rxjs = require("rxjs");
var _rpcCore = _interopRequireDefault(require("@plugnet/rpc-core"));
var _operators = require("rxjs/operators");
var _util = require("@plugnet/util");
// Copyright 2017-2019 @polkadot/rpc-rx authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
/**
* @name RpcRx
* @summary The RxJS API is a wrapper around the API.
* @description It allows wrapping API components with observables using RxJS.
*
* @example
* <BR>
*
* ```javascript
* import RpcRx from '@plugnet/rpc-rx';
* import WsProvider from '@plugnet/rpc-provider/ws';
*
* const provider = new WsProvider('http://127.0.0.1:9944');
* const api = new RpcRx(provider);
* ```
*/
class RpcRx {
/**
* @param {ProviderInterface} provider An API provider using HTTP or WebSocket
*/
constructor(providerOrRpc) {
this._api = void 0;
this._eventemitter = void 0;
this._isConnected = void 0;
this.author = void 0;
this.chain = void 0;
this.state = void 0;
this.system = void 0;
this._api = providerOrRpc instanceof _rpcCore.default ? providerOrRpc : new _rpcCore.default(providerOrRpc);
this._eventemitter = new _eventemitter.default();
this._isConnected = new _rxjs.BehaviorSubject(this._api._provider.isConnected());
this.initEmitters(this._api._provider);
this.author = this.createInterface(this._api.author);
this.chain = this.createInterface(this._api.chain);
this.state = this.createInterface(this._api.state);
this.system = this.createInterface(this._api.system);
}
isConnected() {
return this._isConnected;
}
on(type, handler) {
this._eventemitter.on(type, handler);
}
emit(type) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
this._eventemitter.emit(type, ...args);
}
initEmitters(provider) {
provider.on('connected', () => {
this._isConnected.next(true);
this.emit('connected');
});
provider.on('disconnected', () => {
this._isConnected.next(false);
this.emit('disconnected');
});
}
createInterface(section) {
return Object.keys(section).filter(name => !['subscribe', 'unsubscribe'].includes(name)).reduce((observables, name) => {
observables[name] = this.createObservable(name, section);
return observables;
}, {});
}
createObservable(name, section) {
var _this = this;
if ((0, _util.isFunction)(section[name].unsubscribe)) {
const memoized = (0, _memoizee.default)(function () {
for (var _len2 = arguments.length, params = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
params[_key2] = arguments[_key2];
}
return _this.createReplay(name, params, section, memoized);
}, {
length: false,
// Normalize args so that different args that should be cached
// together are cached together.
// E.g.: `query.my.method('abc') === query.my.method(new AccountId('abc'));`
normalizer: args => {
// `args` is arguments object as accessible in memoized function
return JSON.stringify(args);
}
});
return memoized;
} // We voluntarily don't cache the "one-shot" RPC calls. For example,
// `getStorage('123')` returns the current value, but this value can change
// over time, so we wouldn't want to cache the Observable.
return function () {
for (var _len3 = arguments.length, params = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
params[_key3] = arguments[_key3];
}
return (0, _rxjs.from)(section[name].apply(null, params).catch(error => {
console.error(error);
}));
};
}
createReplay(name, params, section, memoized) {
return new _rxjs.Observable(observer => {
const fn = section[name];
const callback = this.createReplayCallback(observer);
const subscribe = fn(...params, callback).catch(error => observer.next(error));
return this.createReplayUnsub(fn, subscribe, params, memoized);
}).pipe((0, _operators.map)(value => {
if (value instanceof Error) {
throw value;
}
return value;
}), (0, _operators.publishReplay)(1), (0, _operators.refCount)());
}
createReplayCallback(observer) {
let cachedResult;
return result => {
if ((0, _util.isUndefined)(cachedResult) || !Array.isArray(cachedResult) || !Array.isArray(result) || result.length !== cachedResult.length) {
cachedResult = result;
} else {
cachedResult = cachedResult.map((cachedValue, index) => (0, _util.isUndefined)(result[index]) ? cachedValue : result[index]);
}
observer.next(cachedResult);
};
}
createReplayUnsub(fn, subscribe, params, memoized) {
return () => {
subscribe.then(subscriptionId => fn.unsubscribe(subscriptionId)).then(() => {
memoized.delete(...params);
}).catch(error => {
console.error('Unsubscribe failed', error);
});
};
}
}
exports.default = RpcRx;