New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend ApiRx from abstract Base (prepare for promise) #243
Changes from 5 commits
a0f8842
f4ad0a0
ac8a8e6
e23beec
d059469
a6e6963
60e83bf
f11f1ca
37c6cab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
// Copyright 2017-2018 @polkadot/api authors & contributors | ||
// This software may be modified and distributed under the terms | ||
// of the ISC license. See the LICENSE file for details. | ||
|
||
import { ApiBaseInterface } from './types'; | ||
|
||
import E3 from 'eventemitter3'; | ||
import WsProvider from '@polkadot/rpc-provider/ws'; | ||
import RpcRx from '@polkadot/rpc-rx/index'; | ||
import { Extrinsics } from '@polkadot/extrinsics/types'; | ||
import extrinsicsFromMeta from '@polkadot/extrinsics/fromMetadata'; | ||
import { Storage } from '@polkadot/storage/types'; | ||
import storageFromMeta from '@polkadot/storage/fromMetadata'; | ||
import { Hash, Method, RuntimeVersion } from '@polkadot/types/index'; | ||
import RuntimeMetadata from '@polkadot/types/Metadata'; | ||
import assert from '@polkadot/util/assert'; | ||
import isUndefined from '@polkadot/util/is/undefined'; | ||
import logger from '@polkadot/util/logger'; | ||
|
||
type MetaDecoration = { | ||
callIndex?: Uint8Array, | ||
meta: any, | ||
method: string, | ||
section: string, | ||
toJSON: () => any | ||
}; | ||
|
||
const l = logger('api'); | ||
|
||
const INIT_ERROR = `Api needs to be initialised before using, listen on 'ready'`; | ||
|
||
export default abstract class ApiBase<R, S, T> extends E3.EventEmitter implements ApiBaseInterface<R, S, T> { | ||
private _extrinsics?: T; | ||
private _genesisHash?: Hash; | ||
private _storage?: S; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd put protected, if one day someone wants to extend our ApiRx/ApiPromise class There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
private _rpc: R; | ||
private _rpcBase: RpcRx; | ||
private _runtimeMetadata?: RuntimeMetadata; | ||
private _runtimeVersion?: RuntimeVersion; | ||
|
||
/** | ||
* @param wsProvider An optional WebSocket provider from rpc-provider/ws. If not specified, it will default to connecting to the localhost with the default port | ||
* @example | ||
* <BR> | ||
* | ||
* ```javascript | ||
* import Api from '@polkadot/api/rx'; | ||
* | ||
* new Api().isReady.subscribe((api) => { | ||
* api.rpc.newHead().subscribe((header) => { | ||
* console.log(`new block #${header.blockNumber.toNumber()}`); | ||
* }); | ||
* }); | ||
* ``` | ||
*/ | ||
constructor (wsProvider?: WsProvider) { | ||
super(); | ||
|
||
this._rpcBase = new RpcRx(wsProvider); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, you'll have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, that is supposed to be new Rpc There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You'll have a |
||
this._rpc = this.decorateRpc(this._rpcBase); | ||
|
||
this.init(); | ||
} | ||
|
||
/** | ||
* @description Contains the genesis Hash of the attached chain. Apart from being useful to determine the actual chain, it can also be used to sign immortal transactions. | ||
*/ | ||
get genesisHash (): Hash { | ||
assert(!isUndefined(this._genesisHash), INIT_ERROR); | ||
|
||
return this._genesisHash as Hash; | ||
} | ||
|
||
/** | ||
* @description Yields the current attached runtime metadata. Generally this is only used to construct extrinsics & storage, but is useful for current runtime inspection. | ||
*/ | ||
get runtimeMetadata (): RuntimeMetadata { | ||
assert(!isUndefined(this._runtimeMetadata), INIT_ERROR); | ||
|
||
return this._runtimeMetadata as RuntimeMetadata; | ||
} | ||
|
||
/** | ||
* @description Contains the version information for the current runtime. | ||
*/ | ||
get runtimeVersion (): RuntimeVersion { | ||
assert(!isUndefined(this._runtimeVersion), INIT_ERROR); | ||
|
||
return this._runtimeVersion as RuntimeVersion; | ||
} | ||
|
||
/** | ||
* @description Contains all the raw rpc sections and their subsequent methods in the API as defined by the jsonrpc interface definitions. | ||
* @example | ||
* <BR> | ||
* | ||
* ```javascript | ||
* api.rpc.chain | ||
* .newHead() | ||
* .subscribe((header) => { | ||
* console.log('new header', header); | ||
* }); | ||
* ``` | ||
*/ | ||
get rpc (): R { | ||
return this._rpc; | ||
} | ||
|
||
/** | ||
* @description Contains all the chain state modules and their subsequent methods in the API. These are attached dynamically from the runtime metadata. | ||
* @example | ||
* <BR> | ||
* | ||
* ```javascript | ||
* api.st.balances | ||
* .freeBalance(<accountId>) | ||
* .subscribe((balance) => { | ||
* console.log('new balance', balance); | ||
* }); | ||
* ``` | ||
*/ | ||
get st (): S { | ||
assert(!isUndefined(this._storage), INIT_ERROR); | ||
|
||
return this._storage as S; | ||
} | ||
|
||
/** | ||
* @description Contains all the extrinsic modules and their subsequent methods in the API. It allows for the construction of transactions and the submission thereof. These are attached dynamically from the runtime metadata. | ||
* @example | ||
* <BR> | ||
* | ||
* ```javascript | ||
* api.tx.balances | ||
* .transfer(<recipientId>, <balance>) | ||
* .sign(<keyPair>, <accountNonce>, <blockHash (optional)>) | ||
* .send() | ||
* .subscribe((status) => { | ||
* console.log('tx status', status); | ||
* }); | ||
* ``` | ||
*/ | ||
get tx (): T { | ||
assert(!isUndefined(this._extrinsics), INIT_ERROR); | ||
|
||
return this._extrinsics as T; | ||
} | ||
|
||
private init (): void { | ||
let isReady: boolean = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this redundant to the isReady property? One idea is to have isReady as an Observable on this abstract class, and in the Promise implementation, add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went down that route, I think it is easier to just emit the 'ready' and the extention can do what it wants. Will have to see with promise, maybe you are right here, I'm not 100% either way. |
||
|
||
this._rpcBase.on('disconnected', () => { | ||
this.emit('disconnected'); | ||
}); | ||
|
||
this._rpcBase.on('connected', async () => { | ||
this.emit('connected'); | ||
|
||
// TODO When re-connected (i.e. disconnected and then connected), we want to do a couple of things | ||
// - refresh metadata as needed, decorating again | ||
// - re-create storage subscriptions for those we already have | ||
// - re-watch extrinsics where we have subscriptions already | ||
// - need to refresh genesisHash, extrinsic resub only when it matches | ||
if (isReady) { | ||
return; | ||
} | ||
|
||
const hasMeta = await this.loadMeta(); | ||
|
||
if (hasMeta && !isReady) { | ||
isReady = true; | ||
|
||
this.emit('ready', this); | ||
} | ||
}); | ||
} | ||
|
||
private async loadMeta (): Promise<boolean> { | ||
try { | ||
this._runtimeMetadata = await this._rpcBase.state.getMetadata().toPromise(); | ||
this._runtimeVersion = await this._rpcBase.chain.getRuntimeVersion().toPromise(); | ||
this._genesisHash = await this._rpcBase.chain.getBlockHash(0).toPromise(); | ||
|
||
const extrinsics = extrinsicsFromMeta(this.runtimeMetadata); | ||
const storage = storageFromMeta(this.runtimeMetadata); | ||
|
||
this._extrinsics = this.decorateExtrinsics(extrinsics); | ||
this._storage = this.decorateStorage(storage); | ||
|
||
Method.injectExtrinsics(extrinsics); | ||
|
||
return true; | ||
} catch (error) { | ||
l.error('loadMeta', error); | ||
|
||
return false; | ||
} | ||
} | ||
|
||
protected decorateFunctionMeta (input: MetaDecoration, output: MetaDecoration): MetaDecoration { | ||
output.meta = input.meta; | ||
output.method = input.method; | ||
output.section = input.section; | ||
output.toJSON = input.toJSON; | ||
|
||
if (input.callIndex) { | ||
output.callIndex = input.callIndex; | ||
} | ||
|
||
return output; | ||
} | ||
|
||
protected abstract decorateRpc (rpc: RpcRx): R; | ||
protected abstract decorateExtrinsics (extrinsics: Extrinsics): T; | ||
protected abstract decorateStorage (storage: Storage): S; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extrinsics are
T
here, and inE
inApiBaseInterface
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Erk, will fix.