From 8a6d23d46155a45221195cf27d4d5af702c9d801 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 10 Oct 2019 18:28:15 +0200 Subject: [PATCH] Allow limited (non-sub operation) with Http provider (#1465) * Allow limited (non-sub operation) with Http provider * Throw-away typo --- packages/api/src/base/Decorate.ts | 46 ++++++++++++++------- packages/api/src/base/Init.ts | 6 ++- packages/api/src/base/index.ts | 6 +-- packages/api/src/submittable/Submittable.ts | 4 +- packages/types/src/codec/UInt.ts | 2 +- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index 6cd304a710..53fd7e21fa 100644 --- a/packages/api/src/base/Decorate.ts +++ b/packages/api/src/base/Decorate.ts @@ -129,6 +129,13 @@ export default abstract class Decorate extends Events { this._isConnected = new BehaviorSubject(this._rpcCore.provider.isConnected()); } + /** + * @returns `true` if the API operates with subscriptions + */ + get hasSubscriptions (): boolean { + return this._rpcCore.provider.hasSubscriptions; + } + private decorateFunctionMeta (input: MetaDecoration, output: MetaDecoration): MetaDecoration { output.meta = input.meta; output.method = input.method; @@ -185,10 +192,13 @@ export default abstract class Decorate extends Events { // out and section here are horrors to get right from a typing perspective :() (out as any)[sectionName] = Object.entries(rpc[sectionName]).reduce((section, [methodName, method]): DecoratedRpcSection => { - (section as any)[methodName] = decorateMethod(method, { methodName }); + // skip subscriptions where we have a non-subscribable interface + if (this.hasSubscriptions || !(methodName.startsWith('subscribe') || methodName.startsWith('unsubscribe'))) { + (section as any)[methodName] = decorateMethod(method, { methodName }); - // add this endpoint mapping to our internal map - we use this for filters - this._rpcMap.set(`${sectionName}_${methodName}`, jsonrpc[sectionName].methods[methodName]); + // add this endpoint mapping to our internal map - we use this for filters + this._rpcMap.set(`${sectionName}_${methodName}`, jsonrpc[sectionName].methods[methodName]); + } return section; }, {} as unknown as DecoratedRpcSection); @@ -246,15 +256,19 @@ export default abstract class Decorate extends Events { ? [creator, args] : [creator, ...args]; - const decorated = creator.headKey + // FIXME We probably want to be able to query the full list with non-subs as well + const decorated = this.hasSubscriptions && creator.headKey ? this.decorateStorageEntryLinked(creator, decorateMethod) - : decorateMethod((...args: any[]): Observable => - this._rpcCore.state - // Unfortunately for one-shot calls we also use .subscribeStorage here - .subscribeStorage<[Codec]>([getArgs(...args)]) - // state_storage returns an array of values, since we have just subscribed to - // a single entry, we pull that from the array and return it as-is - .pipe(map(([data]): Codec => data)), { methodName: creator.method }); + : decorateMethod((...args: any[]): Observable => ( + this.hasSubscriptions + ? this._rpcCore.state + // Unfortunately for one-shot calls we also use .subscribeStorage here + .subscribeStorage<[Codec]>([getArgs(...args)]) + // state_storage returns an array of values, since we have just subscribed to + // a single entry, we pull that from the array and return it as-is + .pipe(map(([data]): Codec => data)) + : this._rpcCore.state.getStorage(getArgs(...args)) + ), { methodName: creator.method }); decorated.creator = creator; @@ -267,10 +281,12 @@ export default abstract class Decorate extends Events { decorated.key = (arg1?: Arg, arg2?: Arg): string => u8aToHex(compactStripLength(creator(creator.meta.type.isDoubleMap ? [arg1, arg2] : arg1))[1]); - // When using double map storage function, user need to pass double map key as an array - decorated.multi = decorateMethod((args: (Arg | Arg[])[]): Observable => - this._rpcCore.state.subscribeStorage( - args.map((arg: Arg[] | Arg): [StorageEntry, Arg | Arg[]] => [creator, arg]))); + if (this.hasSubscriptions) { + // When using double map storage function, user need to pass double map key as an array + decorated.multi = decorateMethod((args: (Arg | Arg[])[]): Observable => + this._rpcCore.state.subscribeStorage( + args.map((arg: Arg[] | Arg): [StorageEntry, Arg | Arg[]] => [creator, arg]))); + } decorated.size = decorateMethod((arg1?: Arg, arg2?: Arg): Observable => this._rpcCore.state.getStorageSize(getArgs(arg1, arg2))); diff --git a/packages/api/src/base/Init.ts b/packages/api/src/base/Init.ts index 072c555f3f..3efbc5e444 100644 --- a/packages/api/src/base/Init.ts +++ b/packages/api/src/base/Init.ts @@ -11,7 +11,7 @@ import extrinsicsFromMeta from '@polkadot/api-metadata/extrinsics/fromMetadata'; import storageFromMeta from '@polkadot/api-metadata/storage/fromMetadata'; import { GenericCall, GenericEvent, Metadata, u32 as U32 } from '@polkadot/types'; import { LATEST_VERSION as EXTRINSIC_LATEST_VERSION } from '@polkadot/types/primitive/Extrinsic/constants'; -import { assert, logger } from '@polkadot/util'; +import { logger } from '@polkadot/util'; import { cryptoWaitReady, setSS58Format } from '@polkadot/util-crypto'; import addressDefaults from '@polkadot/util-crypto/address/defaults'; @@ -59,7 +59,9 @@ export default abstract class Init extends Decorate { public constructor (options: ApiOptions, type: ApiTypes, decorateMethod: DecorateMethod) { super(options, type, decorateMethod); - assert(this._rpcCore.provider.hasSubscriptions, 'Api can only be used with a provider supporting subscriptions'); + if (!this.hasSubscriptions) { + console.warn('Api will be available in a limited mode since the provider does not support subscriptions'); + } // We only register the types (global) if this is not a cloned instance. // Do right up-front, so we get in the user types before we are actually diff --git a/packages/api/src/base/index.ts b/packages/api/src/base/index.ts index a34b3d74fe..3f3c8f337c 100644 --- a/packages/api/src/base/index.ts +++ b/packages/api/src/base/index.ts @@ -28,7 +28,7 @@ try { } function assertResult (value: T | undefined): T { - assert(!isUndefined(value), 'Api needs to be initialised before using, listen on \'ready\''); + assert(!isUndefined(value), 'Api needs to be initialized before using, listen on \'ready\''); return value as T; } @@ -163,7 +163,7 @@ export default abstract class ApiBase extends Init { /** * @description Contains all the raw rpc sections and their subsequent methods in the API as defined by the jsonrpc interface definitions. Unlike the dynamic `api.query` and `api.tx` sections, these methods are fixed (although extensible with node upgrades) and not determined by the runtime. * - * RPC endpoints available here allow for the query of chain, node and system information, in addition to providing interfaces for the raw queries of state (usine known keys) and the submission of transactions. + * RPC endpoints available here allow for the query of chain, node and system information, in addition to providing interfaces for the raw queries of state (using known keys) and the submission of transactions. * * @example *
@@ -225,7 +225,7 @@ export default abstract class ApiBase extends Init { } /** - * @description Disconnect from the underlying provider, halting all comms + * @description Disconnect from the underlying provider, halting all network traffic */ public disconnect (): void { this._rpcCore.disconnect(); diff --git a/packages/api/src/submittable/Submittable.ts b/packages/api/src/submittable/Submittable.ts index a08a6bfb41..3ac3363782 100644 --- a/packages/api/src/submittable/Submittable.ts +++ b/packages/api/src/submittable/Submittable.ts @@ -72,7 +72,7 @@ export default class Submittable extends _Extrinsic implements Submitta // signAndSend implementation for all 3 cases above public signAndSend (account: IKeyringPair | string | AccountId | Address, optionsOrStatus?: Partial | Callback, optionalStatusCb?: Callback): SubmitableResultResult | SubmitableResultSubscription { const [options, statusCb] = this._makeSignAndSendOptions(optionsOrStatus, optionalStatusCb); - const isSubscription = this._ignoreStatusCb || !!statusCb; + const isSubscription = this._api.hasSubscriptions && (this._ignoreStatusCb || !!statusCb); const address = isKeyringPair(account) ? account.address : account.toString(); let updateId: number | undefined; @@ -106,7 +106,7 @@ export default class Submittable extends _Extrinsic implements Submitta // send implementation for both immediate Hash and statusCb variants public send (statusCb?: Callback): SubmitableResultResult | SubmitableResultSubscription { - const isSubscription = this._ignoreStatusCb || !!statusCb; + const isSubscription = this._api.hasSubscriptions && (this._ignoreStatusCb || !!statusCb); return this._decorateMethod( isSubscription diff --git a/packages/types/src/codec/UInt.ts b/packages/types/src/codec/UInt.ts index c853c76b6f..a4c6a96760 100644 --- a/packages/types/src/codec/UInt.ts +++ b/packages/types/src/codec/UInt.ts @@ -48,7 +48,7 @@ export default class UInt extends AbstractInt { */ public toRawType (): string { // NOTE In the case of balances, which have a special meaning on the UI - // and can be interpreted differently, return a specifc value for it so + // and can be interpreted differently, return a specific value for it so // underlying it always matches (no matter which length it actually is) return this instanceof ClassOf('Balance') ? 'Balance'