UNPKG

@polkadot/react-api

Version:

A collection of RxJs React components the Polkadot JS API

257 lines (202 loc) 8.83 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = withCall; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireDefault(require("react")); var _util = require("@polkadot/util"); var _util2 = require("../util"); var _echo = _interopRequireDefault(require("../transform/echo")); var _api = _interopRequireDefault(require("./api")); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } const NOOP = () => {// ignore }; const NO_SKIP = () => false; // a mapping of actual error messages that has already been shown const errorred = {}; function withCall(endpoint, { at, atProp, callOnResult, fallbacks, isMulti = false, params = [], paramName, paramPick, paramValid = false, propName, skipIf = NO_SKIP, transform = _echo.default, withIndicator = false } = {}) { return Inner => { class WithPromise extends _react.default.Component { constructor(props) { super(props); this.state = { callResult: undefined, callUpdated: false, callUpdatedAt: 0 }; this.destroy = void 0; this.isActive = false; this.propName = void 0; this.timerId = -1; this.constructApiSection = endpoint => { const { api } = this.props; const [area, section, method, ...others] = endpoint.split('.'); (0, _util.assert)(area.length && section.length && method.length && others.length === 0, "Invalid API format, expected <area>.<section>.<method>, found ".concat(endpoint)); (0, _util.assert)(['consts', 'rpc', 'query', 'derive'].includes(area), "Unknown api.".concat(area, ", expected consts, rpc, query or derive")); (0, _util.assert)(!at || area === 'query', 'Only able to do an \'at\' query on the api.query interface'); const apiSection = api[area][section]; return [apiSection, area, section, method]; }; const [, _section, _method] = endpoint.split('.'); this.propName = "".concat(_section, "_").concat(_method); } componentDidUpdate(prevProps) { const oldParams = this.getParams(prevProps); const newParams = this.getParams(this.props); if (this.isActive && !(0, _util2.isEqual)(newParams, oldParams)) { this.subscribe(newParams).then(NOOP).catch(NOOP); } } componentDidMount() { this.isActive = true; if (withIndicator) { this.timerId = window.setInterval(() => { const elapsed = Date.now() - (this.state.callUpdatedAt || 0); const callUpdated = elapsed <= 1500; if (callUpdated !== this.state.callUpdated) { this.nextState({ callUpdated }); } }, 500); } // The attachment takes time when a lot is available, set a timeout // to first handle the current queue before subscribing setImmediate(() => { this.subscribe(this.getParams(this.props)).then(NOOP).catch(NOOP); }); } componentWillUnmount() { this.isActive = false; this.unsubscribe().then(NOOP).catch(NOOP); if (this.timerId !== -1) { clearInterval(this.timerId); } } nextState(state) { if (this.isActive) { this.setState(state); } } getParams(props) { const paramValue = paramPick ? paramPick(props) : paramName ? props[paramName] : undefined; if (atProp) { at = props[atProp]; } // When we are specifying a param and have an invalid, don't use it. For 'params', // we default to the original types, i.e. no validation (query app uses this) if (!paramValid && paramName && ((0, _util.isUndefined)(paramValue) || (0, _util.isNull)(paramValue))) { return [false, []]; } const values = (0, _util.isUndefined)(paramValue) ? params : params.concat(Array.isArray(paramValue) && !paramValue.toU8a ? paramValue : [paramValue]); return [true, values]; } getApiMethod(newParams) { if (endpoint === 'subscribe') { const [fn, ...params] = newParams; return [fn, params, 'subscribe']; } const endpoints = [endpoint].concat(fallbacks || []); const expanded = endpoints.map(this.constructApiSection); const [apiSection, area, section, method] = expanded.find(([apiSection]) => !!apiSection) || [{}, expanded[0][1], expanded[0][2], expanded[0][3]]; (0, _util.assert)(apiSection && apiSection[method], "Unable to find api.".concat(area, ".").concat(section, ".").concat(method)); const meta = apiSection[method].meta; if (area === 'query' && (meta === null || meta === void 0 ? void 0 : meta.type.isMap)) { const arg = newParams[0]; (0, _util.assert)(!(0, _util.isUndefined)(arg) && !(0, _util.isNull)(arg) || meta.type.asMap.kind.isLinkedMap, "".concat(meta.name, " expects one argument")); } return [apiSection[method], newParams, method.startsWith('subscribe') ? 'subscribe' : area]; } async subscribe([isValid, newParams]) { if (!isValid || skipIf(this.props)) { return; } const { api } = this.props; let info; await api.isReady; try { (0, _util.assert)(at || !atProp, 'Unable to perform query on non-existent at hash'); info = this.getApiMethod(newParams); } catch (error) { // don't flood the console with the same errors each time, just do it once, then // ignore it going forward if (!errorred[error.message]) { console.warn(endpoint, '::', error); errorred[error.message] = true; } } if (!info) { return; } const [apiMethod, params, area] = info; const updateCb = value => this.triggerUpdate(this.props, value); await this.unsubscribe(); try { if (['derive', 'subscribe'].includes(area) || area === 'query' && !at && !atProp) { this.destroy = isMulti ? await apiMethod.multi(params, updateCb) : await apiMethod(...params, updateCb); } else if (area === 'consts') { updateCb(apiMethod); } else { updateCb(at ? await apiMethod.at(at, ...params) : await apiMethod(...params)); } } catch (error) {// console.warn(endpoint, '::', error); } } // eslint-disable-next-line @typescript-eslint/require-await async unsubscribe() { if (this.destroy) { this.destroy(); this.destroy = undefined; } } triggerUpdate(props, value) { try { const callResult = (props.transform || transform)(value); if (!this.isActive || (0, _util2.isEqual)(callResult, this.state.callResult)) { return; } (0, _util2.triggerChange)(callResult, callOnResult, props.callOnResult); this.nextState({ callResult, callUpdated: true, callUpdatedAt: Date.now() }); } catch (error) {// console.warn(endpoint, '::', error.message); } } render() { const { callUpdated, callUpdatedAt, callResult } = this.state; const _props = _objectSpread({}, this.props, { callUpdated, callUpdatedAt }); if (!(0, _util.isUndefined)(callResult)) { _props[propName || this.propName] = callResult; } return _react.default.createElement(Inner, _props); } } return (0, _api.default)(WithPromise); }; }