diff --git a/packages/api-derive/src/accounts/idAndIndex.ts b/packages/api-derive/src/accounts/idAndIndex.ts index 8ea3d74d57..9f2d6e392c 100644 --- a/packages/api-derive/src/accounts/idAndIndex.ts +++ b/packages/api-derive/src/accounts/idAndIndex.ts @@ -5,17 +5,47 @@ import { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces'; import { Observable, of } from 'rxjs'; -import { map, startWith } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { isU8a } from '@polkadot/util'; import { decodeAddress } from '@polkadot/util-crypto'; import { createType } from '@polkadot/types'; import { idToIndex } from './idToIndex'; import { indexToId } from './indexToId'; -import { drr } from '../util/drr'; +import { drr } from '../util'; + +interface Calls { + idToIndexCall (accountId: string | AccountId): Observable; + indexToIdCall (accountIndex: string | AccountIndex): Observable; +} export type AccountIdAndIndex = [AccountId?, AccountIndex?]; +function retrieve (address: Address | AccountId | AccountIndex | string | null | undefined, { idToIndexCall, indexToIdCall }: Calls): Observable { + try { + // yes, this can fail, don't care too much, catch will catch it + const decoded = isU8a(address) + ? address + : decodeAddress((address || '').toString()); + + if (decoded.length === 32) { + const accountId = createType('AccountId', decoded); + + return idToIndexCall(accountId).pipe( + map((accountIndex): AccountIdAndIndex => [accountId, accountIndex] as AccountIdAndIndex) + ); + } + + const accountIndex = createType('AccountIndex', decoded); + + return indexToIdCall(accountIndex).pipe( + map((accountId): AccountIdAndIndex => [accountId, accountIndex] as AccountIdAndIndex) + ); + } catch (error) { + return of([undefined, undefined] as AccountIdAndIndex); + } +} + /** * @name idAndIndex * @param {(Address | AccountId | AccountIndex | string | null)} address - An accounts address in various formats. @@ -30,35 +60,11 @@ export type AccountIdAndIndex = [AccountId?, AccountIndex?]; * ``` */ export function idAndIndex (api: ApiInterfaceRx): (address?: Address | AccountId | AccountIndex | string | null) => Observable { - const idToIndexCall = idToIndex(api); - const indexToIdCall = indexToId(api); - - return (address?: Address | AccountId | AccountIndex | string | null): Observable => { - try { - // yes, this can fail, don't care too much, catch will catch it - const decoded = isU8a(address) - ? address - : decodeAddress((address || '').toString()); - - if (decoded.length === 32) { - const accountId = createType('AccountId', decoded); - - return idToIndexCall(accountId).pipe( - startWith(undefined), - map((accountIndex): AccountIdAndIndex => [accountId, accountIndex] as AccountIdAndIndex), - drr() - ); - } - - const accountIndex = createType('AccountIndex', decoded); - - return indexToIdCall(accountIndex).pipe( - startWith(undefined), - map((accountId): AccountIdAndIndex => [accountId, accountIndex] as AccountIdAndIndex), - drr() - ); - } catch (error) { - return of([undefined, undefined] as AccountIdAndIndex).pipe(drr()); - } + const calls = { + idToIndexCall: idToIndex(api), + indexToIdCall: indexToId(api) }; + + return (address?: Address | AccountId | AccountIndex | string | null): Observable => + retrieve(address, calls).pipe(drr()); } diff --git a/packages/api-derive/src/accounts/idToIndex.ts b/packages/api-derive/src/accounts/idToIndex.ts index 52a2a0ac6e..a294a85fff 100644 --- a/packages/api-derive/src/accounts/idToIndex.ts +++ b/packages/api-derive/src/accounts/idToIndex.ts @@ -9,7 +9,7 @@ import { map, startWith } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { indexes, AccountIndexes } from './indexes'; -import { drr } from '../util/drr'; +import { drr } from '../util'; /** * @name idToIndex diff --git a/packages/api-derive/src/accounts/indexToId.ts b/packages/api-derive/src/accounts/indexToId.ts index 5da8eb6a36..ec1f74a1ff 100644 --- a/packages/api-derive/src/accounts/indexToId.ts +++ b/packages/api-derive/src/accounts/indexToId.ts @@ -10,7 +10,7 @@ import { ApiInterfaceRx } from '@polkadot/api/types'; import { ENUMSET_SIZE } from '@polkadot/types/primitive/Generic/AccountIndex'; import { createType, ClassOf, Vec } from '@polkadot/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; /** * @name indexToId diff --git a/packages/api-derive/src/accounts/indexes.ts b/packages/api-derive/src/accounts/indexes.ts index a8fb4d095d..e98eb6f84e 100644 --- a/packages/api-derive/src/accounts/indexes.ts +++ b/packages/api-derive/src/accounts/indexes.ts @@ -10,7 +10,7 @@ import { map, switchMap } from 'rxjs/operators'; import { ENUMSET_SIZE } from '@polkadot/types/primitive/Generic/AccountIndex'; import { createType } from '@polkadot/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; export type AccountIndexes = Record; diff --git a/packages/api-derive/src/accounts/info.ts b/packages/api-derive/src/accounts/info.ts index 73331dbeab..ec629188e8 100644 --- a/packages/api-derive/src/accounts/info.ts +++ b/packages/api-derive/src/accounts/info.ts @@ -7,39 +7,44 @@ import { AccountId, AccountIndex, Address, Balance } from '@polkadot/types/inter import { Codec } from '@polkadot/types/types'; import { DeriveAccountInfo } from '../types'; -import { Observable, of } from 'rxjs'; +import { Observable, of, combineLatest } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { Bytes, Option, u32 } from '@polkadot/types'; import { u8aToString } from '@polkadot/util'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { idAndIndex } from './idAndIndex'; +function retrieveNick (api: ApiInterfaceRx): (accountId?: AccountId) => Observable | undefined> { + return (accountId?: AccountId): Observable | undefined> => + accountId && api.query.nicks + ? api.query.nicks.nameOf>(accountId) + : of(undefined); +} + /** * @name info * @description Returns aux. info with regards to an account, current that includes the accountId, accountIndex and nickname */ export function info (api: ApiInterfaceRx): (address?: AccountIndex | AccountId | Address | string | null) => Observable { const idAndIndexCall = idAndIndex(api); + const nickCall = retrieveNick(api); - // TODO We would really like to pass in an Address or AccountIndex here as well return (address?: AccountIndex | AccountId | Address | string | null): Observable => idAndIndexCall(address).pipe( - switchMap(([accountId, accountIndex]): Observable => - accountId && api.query.nicks - ? api.query.nicks - .nameOf>(accountId) - .pipe( - map((result): DeriveAccountInfo => ({ - accountId, - accountIndex, - nickname: result.isSome - ? u8aToString(result.unwrap()[0]).substr(0, (api.consts.nicks.maxLength as u32).toNumber()) - : undefined - })) - ) - : of({ accountId, accountIndex }) - ), + switchMap(([accountId, accountIndex]): Observable<[DeriveAccountInfo, Option<[Bytes, Balance] & Codec>?]> => { + return combineLatest([ + of({ accountId, accountIndex }), + nickCall(accountId) + ]); + }), + map(([{ accountId, accountIndex }, nameOf]): DeriveAccountInfo => ({ + accountId, + accountIndex, + nickname: nameOf && nameOf.isSome + ? u8aToString(nameOf.unwrap()[0]).substr(0, (api.consts.nicks.maxLength as u32).toNumber()) + : undefined + })), drr() ); } diff --git a/packages/api-derive/src/balances/all.ts b/packages/api-derive/src/balances/all.ts index e3cc16d55d..4015016085 100644 --- a/packages/api-derive/src/balances/all.ts +++ b/packages/api-derive/src/balances/all.ts @@ -14,7 +14,7 @@ import { bnMax } from '@polkadot/util'; import { info } from '../accounts/info'; import { bestNumber } from '../chain/bestNumber'; import { DerivedBalances } from '../types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; type ResultBalance = [Balance, Balance, BalanceLock[], Option]; type Result = [AccountId, BlockNumber, ResultBalance, Index]; diff --git a/packages/api-derive/src/balances/fees.ts b/packages/api-derive/src/balances/fees.ts index 4823a075ad..40c1d080fa 100644 --- a/packages/api-derive/src/balances/fees.ts +++ b/packages/api-derive/src/balances/fees.ts @@ -9,7 +9,7 @@ import { DerivedFees } from '../types'; import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { drr } from '../util/drr'; +import { drr } from '../util'; type Result = [Balance, Balance, Balance, Balance, Balance]; diff --git a/packages/api-derive/src/balances/votingBalances.ts b/packages/api-derive/src/balances/votingBalances.ts index 15bee7c26d..d886a03529 100644 --- a/packages/api-derive/src/balances/votingBalances.ts +++ b/packages/api-derive/src/balances/votingBalances.ts @@ -8,7 +8,7 @@ import { combineLatest, Observable, of } from 'rxjs'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { DerivedBalances } from '../types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { all } from './all'; export function votingBalances (api: ApiInterfaceRx): (addresses?: (AccountId | AccountIndex | Address | string)[]) => Observable { diff --git a/packages/api-derive/src/balances/votingBalancesNominatorsFor.ts b/packages/api-derive/src/balances/votingBalancesNominatorsFor.ts index 4363ef34ab..88302f684c 100644 --- a/packages/api-derive/src/balances/votingBalancesNominatorsFor.ts +++ b/packages/api-derive/src/balances/votingBalancesNominatorsFor.ts @@ -11,7 +11,7 @@ import { Vec } from '@polkadot/types'; import { info } from '../accounts/info'; import { DerivedBalances } from '../types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { votingBalances } from './votingBalances'; export function votingBalancesNominatorsFor (api: ApiInterfaceRx): (address: AccountId | AccountIndex | Address | string) => Observable { diff --git a/packages/api-derive/src/chain/bestNumber.ts b/packages/api-derive/src/chain/bestNumber.ts index 0d3d465bc2..fcf06ed9ab 100644 --- a/packages/api-derive/src/chain/bestNumber.ts +++ b/packages/api-derive/src/chain/bestNumber.ts @@ -8,7 +8,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; /** * @name bestNumber diff --git a/packages/api-derive/src/chain/bestNumberFinalized.ts b/packages/api-derive/src/chain/bestNumberFinalized.ts index 6b373197ef..6462c6ec6b 100644 --- a/packages/api-derive/src/chain/bestNumberFinalized.ts +++ b/packages/api-derive/src/chain/bestNumberFinalized.ts @@ -8,7 +8,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; /** * @name bestNumberFinalized diff --git a/packages/api-derive/src/chain/bestNumberLag.ts b/packages/api-derive/src/chain/bestNumberLag.ts index e808dc4157..9be067e986 100644 --- a/packages/api-derive/src/chain/bestNumberLag.ts +++ b/packages/api-derive/src/chain/bestNumberLag.ts @@ -9,7 +9,7 @@ import { map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { createType } from '@polkadot/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { bestNumber } from './bestNumber'; import { bestNumberFinalized } from './bestNumberFinalized'; diff --git a/packages/api-derive/src/chain/getHeader.ts b/packages/api-derive/src/chain/getHeader.ts index 2ad5c1f8b9..1cc983737b 100644 --- a/packages/api-derive/src/chain/getHeader.ts +++ b/packages/api-derive/src/chain/getHeader.ts @@ -7,7 +7,7 @@ import { catchError, map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { HeaderExtended } from '../type'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { HeaderAndValidators } from './subscribeNewHeads'; /** diff --git a/packages/api-derive/src/chain/subscribeNewHeads.ts b/packages/api-derive/src/chain/subscribeNewHeads.ts index 1d40e187ff..a302c99bf9 100644 --- a/packages/api-derive/src/chain/subscribeNewHeads.ts +++ b/packages/api-derive/src/chain/subscribeNewHeads.ts @@ -9,7 +9,7 @@ import { Observable, combineLatest, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { HeaderExtended } from '../type'; -import { drr } from '../util/drr'; +import { drr } from '../util'; export type HeaderAndValidators = [Header, AccountId[]]; diff --git a/packages/api-derive/src/contracts/fees.ts b/packages/api-derive/src/contracts/fees.ts index 556821b381..977d686f16 100644 --- a/packages/api-derive/src/contracts/fees.ts +++ b/packages/api-derive/src/contracts/fees.ts @@ -9,7 +9,7 @@ import BN from 'bn.js'; import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { drr } from '../util/drr'; +import { drr } from '../util'; type ResultV2 = [BN, BN, BN, BN, BN, BN, BN, BN, BN, BN]; diff --git a/packages/api-derive/src/democracy/referendumInfo.ts b/packages/api-derive/src/democracy/referendumInfo.ts index bde0547d1d..4835c17b4e 100644 --- a/packages/api-derive/src/democracy/referendumInfo.ts +++ b/packages/api-derive/src/democracy/referendumInfo.ts @@ -12,7 +12,7 @@ import { Option } from '@polkadot/types'; import { isNull } from '@polkadot/util'; import { ReferendumInfoExtended } from '../type'; -import { drr } from '../util/drr'; +import { drr } from '../util'; export function constructInfo (index: BN | number, optionInfo?: Option): Option { const info = optionInfo diff --git a/packages/api-derive/src/democracy/referendumInfos.ts b/packages/api-derive/src/democracy/referendumInfos.ts index ee13d96bab..8b49e80e1c 100644 --- a/packages/api-derive/src/democracy/referendumInfos.ts +++ b/packages/api-derive/src/democracy/referendumInfos.ts @@ -11,7 +11,7 @@ import { ApiInterfaceRx } from '@polkadot/api/types'; import { Option, Vec } from '@polkadot/types'; import { ReferendumInfoExtended } from '../type'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { constructInfo } from './referendumInfo'; export function referendumInfos (api: ApiInterfaceRx): (ids?: (BN | number)[]) => Observable[]> { diff --git a/packages/api-derive/src/democracy/referendumVotesFor.ts b/packages/api-derive/src/democracy/referendumVotesFor.ts index d3a5fc4964..586d15a453 100644 --- a/packages/api-derive/src/democracy/referendumVotesFor.ts +++ b/packages/api-derive/src/democracy/referendumVotesFor.ts @@ -11,7 +11,7 @@ import { ApiInterfaceRx } from '@polkadot/api/types'; import { Vec, createType } from '@polkadot/types'; import { DerivedBalances, DerivedReferendumVote } from '../types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { votes } from './votes'; import { votingBalances } from '../balances/votingBalances'; diff --git a/packages/api-derive/src/democracy/referendums.ts b/packages/api-derive/src/democracy/referendums.ts index c40d138425..6216dc67da 100644 --- a/packages/api-derive/src/democracy/referendums.ts +++ b/packages/api-derive/src/democracy/referendums.ts @@ -11,7 +11,7 @@ import { ApiInterfaceRx } from '@polkadot/api/types'; import { Option } from '@polkadot/types'; import { ReferendumInfoExtended } from '../type'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { referendumInfos } from './referendumInfos'; export function referendums (api: ApiInterfaceRx): () => Observable[]> { diff --git a/packages/api-derive/src/democracy/votes.ts b/packages/api-derive/src/democracy/votes.ts index 70c32cea2e..3a4a9fa226 100644 --- a/packages/api-derive/src/democracy/votes.ts +++ b/packages/api-derive/src/democracy/votes.ts @@ -9,7 +9,7 @@ import { Observable, of } from 'rxjs'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { Vec } from '@polkadot/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; export function votes (api: ApiInterfaceRx): (referendumId: BN, accountIds?: AccountId[]) => Observable { return (referendumId: BN, accountIds: AccountId[] = []): Observable => { diff --git a/packages/api-derive/src/elections/approvalsOf.ts b/packages/api-derive/src/elections/approvalsOf.ts index 4cbc879e2c..8893438d53 100644 --- a/packages/api-derive/src/elections/approvalsOf.ts +++ b/packages/api-derive/src/elections/approvalsOf.ts @@ -9,7 +9,7 @@ import { Observable } from 'rxjs'; import { Vec } from '@polkadot/types'; import { switchMap, map } from 'rxjs/operators'; import { approvalFlagsToBools } from '../util/approvalFlagsToBools'; -import { drr } from '../util/drr'; +import { drr } from '../util'; /** * @name approvalsOf diff --git a/packages/api-derive/src/elections/approvalsOfAt.ts b/packages/api-derive/src/elections/approvalsOfAt.ts index 547a39c54a..ec4021f1e7 100644 --- a/packages/api-derive/src/elections/approvalsOfAt.ts +++ b/packages/api-derive/src/elections/approvalsOfAt.ts @@ -11,7 +11,7 @@ import { ApiInterfaceRx } from '@polkadot/api/types'; import { Vec } from '@polkadot/types'; import { approvalFlagsToBools } from '../util/approvalFlagsToBools'; -import { drr } from '../util/drr'; +import { drr } from '../util'; /** * @name approvalsOfAt diff --git a/packages/api-derive/src/elections/info.ts b/packages/api-derive/src/elections/info.ts index c035a2f763..097a600bc2 100644 --- a/packages/api-derive/src/elections/info.ts +++ b/packages/api-derive/src/elections/info.ts @@ -11,7 +11,7 @@ import { ApiInterfaceRx } from '@polkadot/api/types'; import { createType, Vec, u32 } from '@polkadot/types'; import { DerivedElectionsInfo } from '../types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; type ResultElectionsInner = [u32, u32, Vec<[AccountId, BlockNumber] & Codec>, SetIndex, BlockNumber, VoteIndex, SetIndex]; type ResultElections = [Vec, ResultElectionsInner]; diff --git a/packages/api-derive/src/elections/voterPositions.ts b/packages/api-derive/src/elections/voterPositions.ts index 969b3b42de..8bff4c6e44 100644 --- a/packages/api-derive/src/elections/voterPositions.ts +++ b/packages/api-derive/src/elections/voterPositions.ts @@ -10,7 +10,7 @@ import { DerivedVoterPositions } from '../types'; import BN from 'bn.js'; import { of, combineLatest, Observable } from 'rxjs'; import { switchMap, map } from 'rxjs/operators'; -import { drr } from '../util/drr'; +import { drr } from '../util'; /** * @name voterPositions diff --git a/packages/api-derive/src/elections/voters.ts b/packages/api-derive/src/elections/voters.ts index 73ddf1de29..ddca6c8dd7 100644 --- a/packages/api-derive/src/elections/voters.ts +++ b/packages/api-derive/src/elections/voters.ts @@ -10,7 +10,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { createType, Vec } from '@polkadot/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { voterPositions } from './voterPositions'; /** diff --git a/packages/api-derive/src/imOnline/receivedHeartbeats.ts b/packages/api-derive/src/imOnline/receivedHeartbeats.ts index fcdc013e75..855bfc03e2 100644 --- a/packages/api-derive/src/imOnline/receivedHeartbeats.ts +++ b/packages/api-derive/src/imOnline/receivedHeartbeats.ts @@ -10,7 +10,7 @@ import { map, switchMap } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { Bytes, Vec, u32 } from '@polkadot/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; /** * @description Return a boolean array indicating whether the passed accounts had received heartbeats in the current session diff --git a/packages/api-derive/src/session/eraLength.ts b/packages/api-derive/src/session/eraLength.ts index 0e68eb0106..26c4df595e 100644 --- a/packages/api-derive/src/session/eraLength.ts +++ b/packages/api-derive/src/session/eraLength.ts @@ -8,7 +8,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { info } from './info'; export function eraLength (api: ApiInterfaceRx): () => Observable { diff --git a/packages/api-derive/src/session/eraProgress.ts b/packages/api-derive/src/session/eraProgress.ts index 3831d5b626..5d3f40923f 100644 --- a/packages/api-derive/src/session/eraProgress.ts +++ b/packages/api-derive/src/session/eraProgress.ts @@ -7,7 +7,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { info } from './info'; export function eraProgress (api: ApiInterfaceRx): () => Observable { diff --git a/packages/api-derive/src/session/info.ts b/packages/api-derive/src/session/info.ts index 3241df8d0b..1b252e2899 100644 --- a/packages/api-derive/src/session/info.ts +++ b/packages/api-derive/src/session/info.ts @@ -10,7 +10,7 @@ import { map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { Option, u64, createType } from '@polkadot/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { bestNumber } from '../chain'; type ResultV1Session = [SessionIndex, Option, BlockNumber, BlockNumber, SessionIndex]; diff --git a/packages/api-derive/src/session/sessionProgress.ts b/packages/api-derive/src/session/sessionProgress.ts index f33344a574..eded8c991a 100644 --- a/packages/api-derive/src/session/sessionProgress.ts +++ b/packages/api-derive/src/session/sessionProgress.ts @@ -8,7 +8,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { info } from './info'; export function sessionProgress (api: ApiInterfaceRx): () => Observable { diff --git a/packages/api-derive/src/staking/all.ts b/packages/api-derive/src/staking/all.ts index 93a85d6c77..25ef1df49e 100644 --- a/packages/api-derive/src/staking/all.ts +++ b/packages/api-derive/src/staking/all.ts @@ -8,7 +8,7 @@ import { Observable, combineLatest } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; import { controllers } from './controllers'; import { info } from './info'; diff --git a/packages/api-derive/src/staking/controllers.ts b/packages/api-derive/src/staking/controllers.ts index 42162901cc..bce9a49e12 100644 --- a/packages/api-derive/src/staking/controllers.ts +++ b/packages/api-derive/src/staking/controllers.ts @@ -10,7 +10,7 @@ import { switchMap } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { Option } from '@polkadot/types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; /** * @description From the list of stash accounts, retrieve the list of controllers diff --git a/packages/api-derive/src/staking/info.ts b/packages/api-derive/src/staking/info.ts index 537a79242c..f661d9c885 100644 --- a/packages/api-derive/src/staking/info.ts +++ b/packages/api-derive/src/staking/info.ts @@ -20,7 +20,7 @@ import { isUndefined } from '@polkadot/util'; import { bestNumber } from '../chain/bestNumber'; import { eraLength } from '../session/eraLength'; import { recentlyOffline } from './recentlyOffline'; -import { drr } from '../util/drr'; +import { drr } from '../util'; interface Calls { bestNumberCall (): Observable; diff --git a/packages/api-derive/src/staking/recentlyOffline.ts b/packages/api-derive/src/staking/recentlyOffline.ts index 6c05a74b9e..6abc74f039 100644 --- a/packages/api-derive/src/staking/recentlyOffline.ts +++ b/packages/api-derive/src/staking/recentlyOffline.ts @@ -11,7 +11,7 @@ import { map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { DerivedRecentlyOffline } from '../types'; -import { drr } from '../util/drr'; +import { drr } from '../util'; type OfflineResult = [AccountId, BlockNumber, BN][] & Codec; diff --git a/packages/api-derive/src/util/index.ts b/packages/api-derive/src/util/index.ts index 28af21e7b0..0504d7a02e 100644 --- a/packages/api-derive/src/util/index.ts +++ b/packages/api-derive/src/util/index.ts @@ -2,4 +2,6 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +export { drr } from '@polkadot/rpc-core/rxjs'; + export * from './approvalFlagsToBools'; diff --git a/packages/rpc-core/src/cached.spec.ts b/packages/rpc-core/src/cached.spec.ts index 1f4225fdfd..b73b8a7ab8 100644 --- a/packages/rpc-core/src/cached.spec.ts +++ b/packages/rpc-core/src/cached.spec.ts @@ -44,22 +44,41 @@ describe('Cached Observables', (): void => { expect(observable2).not.toBe(observable1); }); - it('clears cache if there are no more subscribers', (): void => { + it('subscribes to the same one if within the period (unbsub delay)', (done): void => { + const observable1 = rpc.chain.subscribeNewHeads(); + const sub1 = observable1.subscribe(); + + sub1.unsubscribe(); + + setTimeout((): void => { + const observable2 = rpc.chain.subscribeNewHeads(); + const sub2 = observable2.subscribe(); + + expect(observable1).toBe(observable2); + + sub2.unsubscribe(); + done(); + }, 500); + }); + + it('clears cache if there are no more subscribers', (done): void => { const observable1 = rpc.chain.subscribeNewHeads(); const observable2 = rpc.chain.subscribeNewHeads(); - - expect(observable1).toBe(observable2); - const sub1 = observable1.subscribe(); const sub2 = observable2.subscribe(); + expect(observable1).toBe(observable2); + sub1.unsubscribe(); sub2.unsubscribe(); - // No more subscribers, now create a new observable - const observable3 = rpc.chain.subscribeNewHeads(); + setTimeout((): void => { + // No more subscribers, now create a new observable + const observable3 = rpc.chain.subscribeNewHeads(); - expect(observable3).not.toBe(observable1); + expect(observable3).not.toBe(observable1); + done(); + }, 3500); }); it('creates different observables for different methods but same arguments', (): void => { diff --git a/packages/rpc-core/src/index.ts b/packages/rpc-core/src/index.ts index cd8cd0e9a9..cb45cdc5b1 100644 --- a/packages/rpc-core/src/index.ts +++ b/packages/rpc-core/src/index.ts @@ -10,7 +10,7 @@ import { RpcInterfaceMethod, UserRpc } from './types'; import memoizee from 'memoizee'; import { combineLatest, from, Observable, Observer, of, throwError } from 'rxjs'; -import { catchError, distinctUntilChanged, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; +import { catchError, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; import jsonrpc from '@polkadot/jsonrpc'; import jsonrpcMethod from '@polkadot/jsonrpc/create/method'; import jsonrpcParam from '@polkadot/jsonrpc/create/param'; @@ -18,6 +18,8 @@ import { Option, StorageKey, Vec, createClass } from '@polkadot/types'; import { createTypeUnsafe } from '@polkadot/types/codec'; import { assert, isFunction, isNull, isNumber, logger, u8aToU8a } from '@polkadot/util'; +import { drr } from './rxjs'; + type UserRpcConverted = Record>; const l = logger('rpc-core'); @@ -223,6 +225,19 @@ export default class Rpc implements RpcInterface { return call as RpcInterfaceMethod; } + // create a subscriptor, it subscribes once and resolves with the id as subscribe + private createSubscriber ({ subType, subName, paramsJson, update }: { subType: string; subName: string; paramsJson: AnyJson[]; update: (error?: Error, result?: any) => void }, errorHandler: (error: Error) => void): Promise { + return new Promise((resolve, reject): void => { + this.provider + .subscribe(subType, subName, paramsJson, update) + .then(resolve) + .catch((error): void => { + errorHandler(error); + reject(error); + }); + }); + } + private createMethodSubscribe (method: RpcMethod): RpcInterfaceMethod { const [updateType, subMethod, unsubMethod] = method.pubsub; const subName = `${method.section}_${subMethod}`; @@ -253,9 +268,7 @@ export default class Rpc implements RpcInterface { observer.next(this.formatOutput(method, params, result)); }; - subscriptionPromise = this.provider - .subscribe(subType, subName, paramsJson, update) - .catch(errorHandler); + subscriptionPromise = this.createSubscriber({ subType, subName, paramsJson, update }, errorHandler); } catch (error) { errorHandler(error); } @@ -281,18 +294,9 @@ export default class Rpc implements RpcInterface { ? this.provider.unsubscribe(subType, unsubName, subscriptionId) : Promise.resolve(false) ) - .catch((error: Error): void => - l.error(this.createErrorMessage(method, error)) - ); + .catch((error: Error): void => l.error(this.createErrorMessage(method, error))); }; - }).pipe( - // Duplicated in api-derive/util/drr - distinctUntilChanged((a: any, b: any): boolean => - JSON.stringify({ value: a }) === JSON.stringify({ value: b }) - ), - publishReplay(1), - refCount() - ); + }).pipe(drr()); }; const memoized = memoizee(call, { diff --git a/packages/rpc-core/src/replay.spec.ts b/packages/rpc-core/src/replay.spec.ts index 42829f4758..683a8cad18 100644 --- a/packages/rpc-core/src/replay.spec.ts +++ b/packages/rpc-core/src/replay.spec.ts @@ -57,12 +57,12 @@ describe('replay', (): void => { const subscription = rpc.chain.subscribeNewHeads().subscribe((): void => { subscription.unsubscribe(); - // There's a promise inside .unsubscribe(), wait a bit + // There's a promise inside .unsubscribe(), wait a bit (> 2s) setTimeout((): void => { // eslint-disable-next-line @typescript-eslint/unbound-method expect(rpc.provider.unsubscribe).toHaveBeenCalled(); done(); - }, 200); + }, 3500); }); }); }); diff --git a/packages/api-derive/src/util/drr.spec.ts b/packages/rpc-core/src/rxjs/drr.spec.ts similarity index 100% rename from packages/api-derive/src/util/drr.spec.ts rename to packages/rpc-core/src/rxjs/drr.spec.ts diff --git a/packages/api-derive/src/util/drr.ts b/packages/rpc-core/src/rxjs/drr.ts similarity index 70% rename from packages/api-derive/src/util/drr.ts rename to packages/rpc-core/src/rxjs/drr.ts index 156d75b898..903233a11e 100644 --- a/packages/api-derive/src/util/drr.ts +++ b/packages/rpc-core/src/rxjs/drr.ts @@ -1,11 +1,13 @@ -// Copyright 2017-2019 @polkadot/api-derive authors & contributors +// Copyright 2017-2019 @polkadot/rpc-core authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { catchError, distinctUntilChanged, publishReplay, refCount } from 'rxjs/operators'; import { Observable } from 'rxjs'; +import { catchError, distinctUntilChanged, publishReplay } from 'rxjs/operators'; import { logger } from '@polkadot/util'; +import { refCountDelay } from './refCountDelay'; + type DrrResult = (source$: Observable) => Observable; const l = logger('drr'); @@ -23,8 +25,8 @@ export const drr = (): DrrResult => (source$: Observable): Observable throw error; }), distinctUntilChanged((a: any, b: any): boolean => - JSON.stringify({ value: a }) === JSON.stringify({ value: b }) + JSON.stringify({ t: a }) === JSON.stringify({ t: b }) ), publishReplay(1), - refCount() + refCountDelay() ); diff --git a/packages/rpc-core/src/rxjs/index.ts b/packages/rpc-core/src/rxjs/index.ts new file mode 100644 index 0000000000..7fb8538a81 --- /dev/null +++ b/packages/rpc-core/src/rxjs/index.ts @@ -0,0 +1,6 @@ +// Copyright 2017-2019 @polkadot/rpc-core authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +export * from './drr'; +export * from './refCountDelay'; diff --git a/packages/rpc-core/src/rxjs/refCountDelay.ts b/packages/rpc-core/src/rxjs/refCountDelay.ts new file mode 100644 index 0000000000..b7c99f6a8d --- /dev/null +++ b/packages/rpc-core/src/rxjs/refCountDelay.ts @@ -0,0 +1,55 @@ +// Copyright 2017-2019 @polkadot/rpc-core authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +// From https://medium.com/@volkeron/rxjs-unsubscribe-delay-218a9ab2672e (adapted for rxjs 6) +// v6 inspiration from https://github.com/cartant/rxjs-etc/blob/7d7cd21233f67c364fd246ef72077bbe494dd5c2/source/operators/refCountDelay.ts + +import { asapScheduler, ConnectableObservable, MonoTypeOperatorFunction, NEVER, Observable, Subject, Subscription, timer, using } from 'rxjs'; +import { scan, switchMap, tap } from 'rxjs/operators'; + +const DELAY = 1750; + +// subscribe to a source, tracker and increment the counter +function subscribe (source: Observable, counter: Subject, tracker: Observable): [Subscription, Subscription] { + const sourceConn = (source as ConnectableObservable).connect(); + const trackerConn = tracker.subscribe(); + + counter.next(1); + + return [sourceConn, trackerConn]; +} + +// unsubscribe from both the tracker and the source +function unsubscribe (sourceConn: Subscription | undefined, trackerConn: Subscription | undefined): void { + sourceConn && sourceConn.unsubscribe(); + trackerConn && trackerConn.unsubscribe(); +} + +// keep track of references, but only unsubscribe after the configured delay +export function refCountDelay (): MonoTypeOperatorFunction { + return (source: Observable): Observable => { + let sourceConn: Subscription | undefined; + let trackerConn: Subscription | undefined; + const counter = new Subject(); + const tracker = counter.pipe( + scan((total, change) => change + total, 0), + switchMap((count) => + count === 0 // when we have a zero count, schedule a unsubscribe + ? timer(DELAY, asapScheduler).pipe(tap((): void => unsubscribe(sourceConn, trackerConn))) + : NEVER + ) + ); + + return using( + () => { + [sourceConn, trackerConn] = subscribe(source, counter, tracker); + + return { + unsubscribe: (): void => counter.next(-1) + }; + }, + (): Observable => source + ); + }; +}