From 89217e89d83875da5689810b9902cde8b749f0dc Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 10 Oct 2019 13:29:03 +0200 Subject: [PATCH 1/2] Allow limited (non-sub operation) with Http provider --- 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 +- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index 6cd304a710c2..53fd7e21fa60 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 072c555f3f7e..3efbc5e4442c 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 a34b3d74febb..3f3c8f337cc0 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 a08a6bfb4195..3ac336378210 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 From e4e7eba1c7096286646fe1c26442e879ca2d173d Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 10 Oct 2019 13:31:33 +0200 Subject: [PATCH 2/2] Throw-away typo --- packages/types/src/codec/UInt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/codec/UInt.ts b/packages/types/src/codec/UInt.ts index c853c76b6ff9..a4c6a9676059 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'