Add connected() to API interface (#2580)

* Add connected() to API interface

* Slight doc update

* Align ApiRx

* isConnected() usage

* Updates

* Retrieve isConnected for API via observable

* Remove (now) useless Rx test
This commit is contained in:
Jaco Greeff
2020-09-04 17:57:02 +02:00
committed by GitHub
parent 4f4833da74
commit 00bc441f0e
17 changed files with 125 additions and 72 deletions
+1 -1
View File
@@ -142,7 +142,7 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
this._options = options;
this._type = type;
this._rpcCore = new RpcCore(this.#instanceId, this.#registry, thisProvider, this._options.rpc);
this._isConnected = new BehaviorSubject(this._rpcCore.provider.isConnected());
this._isConnected = new BehaviorSubject(this._rpcCore.provider.isConnected);
this._rx.hasSubscriptions = this._rpcCore.provider.hasSubscriptions;
this._rx.registry = this.#registry;
}
+1 -1
View File
@@ -62,7 +62,7 @@ export default abstract class Init<ApiType extends ApiTypes> extends Decorate<Ap
// If the provider was instantiated earlier, and has already emitted a
// 'connected' event, then the `on('connected')` won't fire anymore. To
// cater for this case, we call manually `this._onProviderConnect`.
if (this._rpcCore.provider.isConnected()) {
if (this._rpcCore.provider.isConnected) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.#onProviderConnect();
}
+16 -2
View File
@@ -117,6 +117,13 @@ export default abstract class ApiBase<ApiType extends ApiTypes> extends Init<Api
return this._rpcCore.provider.hasSubscriptions;
}
/**
* @description true is the underlying provider is connected
*/
public get isConnected (): boolean {
return this._isConnected.getValue();
}
/**
* @description The library information name & version (from package.json)
*/
@@ -232,11 +239,18 @@ export default abstract class ApiBase<ApiType extends ApiTypes> extends Init<Api
return assertResult(this._extrinsics);
}
/**
* @description Connect from the underlying provider, halting all network traffic
*/
public connect (): Promise<void> {
return this._rpcCore.connect();
}
/**
* @description Disconnect from the underlying provider, halting all network traffic
*/
public disconnect (): void {
this._rpcCore.disconnect();
public disconnect (): Promise<void> {
return this._rpcCore.disconnect();
}
/**
-9
View File
@@ -9,7 +9,6 @@ import { map, switchMap } from 'rxjs/operators';
import testingPairs from '@polkadot/keyring/testingPairs';
import Mock from '@polkadot/rpc-provider/mock/index';
import { TypeRegistry } from '@polkadot/types';
import { isObservable } from '@polkadot/util';
import { SingleAccountSigner } from '../../test/util';
import ApiRx from './Api';
@@ -24,14 +23,6 @@ describe('ApiRx', (): void => {
provider = new Mock(registry);
});
it('has isConnected', (): void => {
const api = new ApiRx();
expect(
isObservable(api.isConnected)
).toBe(true);
});
describe('decorator.signAsync', (): void => {
it('signs a transfer using an external signer', (): void => {
const signer = new SingleAccountSigner(registry, keyring.alice_session);
-7
View File
@@ -173,13 +173,6 @@ export default class ApiRx extends ApiBase<'rxjs'> {
);
}
/**
* @description Observable that carries the connected state for the provider. Results in a boolean flag that is true/false based on the connectivity.
*/
public get isConnected (): Observable<boolean> {
return this._isConnected;
}
/**
* @description Observable that returns the first time we are connected and loaded
*/
+16 -2
View File
@@ -129,11 +129,25 @@ export default class Rpc implements RpcInterface {
this.addUserInterfaces(userRpc);
}
/**
* @description Returns the connected status of a provider
*/
public get isConnected (): boolean {
return this.provider.isConnected;
}
/**
* @description Manually connect from the attached provider
*/
public connect (): Promise<void> {
return this.provider.connect();
}
/**
* @description Manually disconnect from the attached provider
*/
public disconnect (): void {
this.provider.disconnect();
public disconnect (): Promise<void> {
return this.provider.disconnect();
}
/**
+1 -1
View File
@@ -31,7 +31,7 @@ describe('Http', (): void => {
});
it('always returns isConnected true', (): void => {
expect(http.isConnected()).toEqual(true);
expect(http.isConnected).toEqual(true);
});
it('does not (yet) support subscribe', (): Promise<number | void> => {
+9 -2
View File
@@ -67,10 +67,17 @@ export default class HttpProvider implements ProviderInterface {
throw new Error('Unimplemented');
}
/**
* @description Manually connect from the connection
*/
public async connect (): Promise<void> {
// noop
}
/**
* @description Manually disconnect from the connection
*/
public disconnect (): void {
public async disconnect (): Promise<void> {
// noop
}
@@ -78,7 +85,7 @@ export default class HttpProvider implements ProviderInterface {
* @summary Whether the node is connected or not.
* @return {boolean} true if connected
*/
public isConnected (): boolean {
public get isConnected (): boolean {
return true;
}
+6 -2
View File
@@ -98,11 +98,15 @@ export default class Mock implements ProviderInterface {
throw new Error('Unimplemented');
}
public disconnect (): void {
public async connect (): Promise<void> {
// noop
}
public isConnected (): boolean {
public async disconnect (): Promise<void> {
// noop
}
public get isConnected (): boolean {
return true;
}
+4 -2
View File
@@ -44,9 +44,11 @@ export type ProviderInterfaceEmitCb = (value?: any) => any;
export interface ProviderInterface {
readonly hasSubscriptions: boolean;
readonly isConnected: boolean;
clone (): ProviderInterface;
disconnect (): void;
isConnected (): boolean;
connect (): Promise<void>;
disconnect (): Promise<void>;
on (type: ProviderInterfaceEmitted, sub: ProviderInterfaceEmitCb): () => void;
send (method: string, params: any[]): Promise<any>;
subscribe (type: string, method: string, params: any[], cb: ProviderInterfaceCallback): Promise<number | string>;
+32 -26
View File
@@ -30,10 +30,6 @@ interface WsStateSubscription extends SubscriptionHandler {
params: any[];
}
interface WSProviderInterface extends ProviderInterface {
connect (): void;
}
const ALIASSES: { [index: string]: string } = {
chain_finalisedHead: 'chain_finalizedHead',
chain_subscribeFinalisedHeads: 'chain_subscribeFinalizedHeads',
@@ -62,7 +58,7 @@ const l = logger('api-ws');
*
* @see [[HttpProvider]]
*/
export default class WsProvider implements WSProviderInterface {
export default class WsProvider implements ProviderInterface {
readonly #coder: Coder;
readonly #endpoints: string[];
@@ -123,6 +119,14 @@ export default class WsProvider implements WSProviderInterface {
return true;
}
/**
* @summary Whether the node is connected or not.
* @return {boolean} true if connected
*/
public get isConnected (): boolean {
return this.#isConnected;
}
/**
* @description Returns a clone of the object
*/
@@ -152,31 +156,34 @@ export default class WsProvider implements WSProviderInterface {
this.#websocket.onopen = this.#onSocketOpen;
} catch (error) {
l.error(error);
this.#emit('error', error);
throw error;
}
}
/**
* @description Manually disconnect from the connection, clearing autoconnect logic
*/
public disconnect (): void {
if (isNull(this.#websocket)) {
throw new Error('Cannot disconnect on a non-open websocket');
// eslint-disable-next-line @typescript-eslint/require-await
public async disconnect (): Promise<void> {
try {
assert(!isNull(this.#websocket), 'Cannot disconnect on a non-connected websocket');
// switch off autoConnect, we are in manual mode now
this.#autoConnectMs = 0;
// 1000 - Normal closure; the connection successfully completed
this.#websocket.close(1000);
this.#websocket = null;
} catch (error) {
l.error(error);
this.#emit('error', error);
throw error;
}
// switch off autoConnect, we are in manual mode now
this.#autoConnectMs = 0;
// 1000 - Normal closure; the connection successfully completed
this.#websocket.close(1000);
this.#websocket = null;
}
/**
* @summary Whether the node is connected or not.
* @return {boolean} true if connected
*/
public isConnected (): boolean {
return this.#isConnected;
}
/**
@@ -220,7 +227,7 @@ export default class WsProvider implements WSProviderInterface {
subscription
};
if (this.isConnected() && !isNull(this.#websocket)) {
if (this.isConnected && !isNull(this.#websocket)) {
this.#websocket.send(json);
} else {
this.#queued[id] = json;
@@ -297,8 +304,7 @@ export default class WsProvider implements WSProviderInterface {
if (this.#autoConnectMs > 0) {
setTimeout((): void => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.connect();
this.connect().catch(() => { /* noop */ });
}, this.#autoConnectMs);
}
}
+8 -8
View File
@@ -30,26 +30,26 @@ describe('onConnect', (): void => {
it('Does not connect when autoConnect is false', (): void => {
const provider: WsProvider = new WsProvider(TEST_WS_URL, 0);
expect(provider.isConnected()).toBe(false);
expect(provider.isConnected).toBe(false);
});
it('Does connect when autoConnect is true', async () => {
const provider: WsProvider = new WsProvider(TEST_WS_URL, 1);
await sleepMs(100); // Hack to give the provider time to connect
expect(provider.isConnected()).toBe(true);
expect(provider.isConnected).toBe(true);
});
it('Creates a new WebSocket instance by calling the connect() method', async () => {
const provider: WsProvider = new WsProvider(TEST_WS_URL, false);
expect(provider.isConnected()).toBe(false);
expect(provider.isConnected).toBe(false);
expect(mocks[0].server.clients().length).toBe(0);
await provider.connect();
await sleepMs(100); // Hack to give the provider time to connect
expect(provider.isConnected()).toBe(true);
expect(provider.isConnected).toBe(true);
expect(mocks[0].server.clients().length).toBe(1);
});
@@ -59,7 +59,7 @@ describe('onConnect', (): void => {
await provider.connect();
await sleepMs(100); // Hack to give the provider time to connect
expect(provider.isConnected()).toBe(true);
expect(provider.isConnected).toBe(true);
});
it('Connects to the second endpoint when the first is unreachable', async () => {
@@ -71,7 +71,7 @@ describe('onConnect', (): void => {
await sleepMs(100); // Hack to give the provider time to connect
expect(provider.isConnected()).toBe(true);
expect(provider.isConnected).toBe(true);
});
it('Connects to the second endpoint when the first is dropped', async () => {
@@ -92,7 +92,7 @@ describe('onConnect', (): void => {
// Check that second server is connected
expect(mocks[1].server.clients().length).toBe(1);
expect(provider.isConnected()).toBe(true);
expect(provider.isConnected).toBe(true);
});
it('Round-robin of endpoints on WsProvider', async () => {
@@ -124,7 +124,7 @@ describe('onConnect', (): void => {
// Check that first server is connected and the next one isn't
expect(mocks[index].server.clients().length).toBe(1);
expect(mockNext[index].server.clients().length).toBe(0);
expect(provider.isConnected()).toBe(true);
expect(provider.isConnected).toBe(true);
// Close connection from first server
mocks[index].server.clients()[0].close();
+1 -1
View File
@@ -25,7 +25,7 @@ describe('Ws', (): void => {
it('returns the connected state', (): void => {
expect(
createWs([]).isConnected()
createWs([]).isConnected
).toEqual(false);
});