Sub/unsub cleanups (#1535)
* Sub/unsub cleanups * drr full idAndIndex * Memo on nick call * refCountDelay * Remove scheduler param, always asap * Flatten * Test cleanups * Make tests pass * Remove logs * Drop nick memo * sourceConnection.unsubscribe * Small cleanups
This commit is contained in:
@@ -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<AccountIndex | undefined>;
|
||||
indexToIdCall (accountIndex: string | AccountIndex): Observable<AccountId | undefined>;
|
||||
}
|
||||
|
||||
export type AccountIdAndIndex = [AccountId?, AccountIndex?];
|
||||
|
||||
function retrieve (address: Address | AccountId | AccountIndex | string | null | undefined, { idToIndexCall, indexToIdCall }: Calls): Observable<AccountIdAndIndex> {
|
||||
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<AccountIdAndIndex> {
|
||||
const idToIndexCall = idToIndex(api);
|
||||
const indexToIdCall = indexToId(api);
|
||||
|
||||
return (address?: Address | AccountId | AccountIndex | string | null): Observable<AccountIdAndIndex> => {
|
||||
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<AccountIdAndIndex> =>
|
||||
retrieve(address, calls).pipe(drr());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<string, AccountIndex>;
|
||||
|
||||
|
||||
@@ -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<Option<[Bytes, Balance] & Codec> | undefined> {
|
||||
return (accountId?: AccountId): Observable<Option<[Bytes, Balance] & Codec> | undefined> =>
|
||||
accountId && api.query.nicks
|
||||
? api.query.nicks.nameOf<Option<[Bytes, Balance] & Codec>>(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<DeriveAccountInfo> {
|
||||
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<DeriveAccountInfo> =>
|
||||
idAndIndexCall(address).pipe(
|
||||
switchMap(([accountId, accountIndex]): Observable<DeriveAccountInfo> =>
|
||||
accountId && api.query.nicks
|
||||
? api.query.nicks
|
||||
.nameOf<Option<[Bytes, Balance] & Codec>>(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()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<VestingSchedule>];
|
||||
type Result = [AccountId, BlockNumber, ResultBalance, Index];
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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<DerivedBalances[]> {
|
||||
|
||||
@@ -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<DerivedBalances[]> {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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[]];
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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<ReferendumInfo>): Option<ReferendumInfoExtended> {
|
||||
const info = optionInfo
|
||||
|
||||
@@ -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<Option<ReferendumInfoExtended>[]> {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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<Option<ReferendumInfoExtended>[]> {
|
||||
|
||||
@@ -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<Vote[]> {
|
||||
return (referendumId: BN, accountIds: AccountId[] = []): Observable<Vote[]> => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<AccountId>, ResultElectionsInner];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<BlockNumber> {
|
||||
|
||||
@@ -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<BN> {
|
||||
|
||||
@@ -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, BlockNumber, SessionIndex];
|
||||
|
||||
@@ -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<BlockNumber> {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<BlockNumber>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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<string, Record<string, RpcMethod>>;
|
||||
|
||||
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<number> {
|
||||
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, {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 = <T> (source$: Observable<T>) => Observable<T>;
|
||||
|
||||
const l = logger('drr');
|
||||
@@ -23,8 +25,8 @@ export const drr = (): DrrResult => <T> (source$: Observable<T>): Observable<T>
|
||||
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()
|
||||
);
|
||||
@@ -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';
|
||||
@@ -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 <T> (source: Observable<T>, counter: Subject<number>, tracker: Observable<number>): [Subscription, Subscription] {
|
||||
const sourceConn = (source as ConnectableObservable<T>).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 <T> (): MonoTypeOperatorFunction<T> {
|
||||
return (source: Observable<T>): Observable<T> => {
|
||||
let sourceConn: Subscription | undefined;
|
||||
let trackerConn: Subscription | undefined;
|
||||
const counter = new Subject<number>();
|
||||
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<T> => source
|
||||
);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user