Polyfill WebSocket with W3C (#32)
* Use W3CSocket from websocket * Handle queueing when not connected (yet) * Assert connection prefixes * Update tests * Bump * Update error checks * Fix polyfill test
This commit is contained in:
@@ -14,9 +14,16 @@ module.exports = class HttpProvider extends JsonRpcCoder implements ProviderInte
|
||||
constructor (endpoint: string) {
|
||||
super();
|
||||
|
||||
assert(endpoint, 'Endpoint should be provided');
|
||||
assert(/^http:\/\//.test(endpoint), `Endpoint should start with 'http://', received '${endpoint}'`);
|
||||
|
||||
this._endpoint = endpoint;
|
||||
}
|
||||
|
||||
get isConnected (): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async send (method: string, params: Array<any>): Promise<any> {
|
||||
const body = this.encodeJson(method, params);
|
||||
const response = await fetch(this._endpoint, {
|
||||
|
||||
@@ -21,7 +21,30 @@ describe('Http', () => {
|
||||
encodeSpy.mockRestore();
|
||||
decodeSpy.mockRestore();
|
||||
|
||||
mock.done();
|
||||
if (mock) {
|
||||
mock.done();
|
||||
mock = null;
|
||||
}
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('requires the endpoint to be set', () => {
|
||||
expect(
|
||||
() => new Http()
|
||||
).toThrow(/should be provided/);
|
||||
});
|
||||
|
||||
it('requires an http:// prefixed endpoint', () => {
|
||||
expect(
|
||||
() => new Http('ws://')
|
||||
).toThrow(/with 'http/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isConnected', () => {
|
||||
it('always returns true', () => {
|
||||
expect(http.isConnected).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('send', () => {
|
||||
|
||||
@@ -22,5 +22,6 @@ export type JsonRpcResponseBase = {
|
||||
export type JsonRpcResponse = JsonRpcObject & JsonRpcResponseBase;
|
||||
|
||||
export interface ProviderInterface {
|
||||
+isConnected: boolean;
|
||||
send (method: string, params: Array<any>): Promise<any>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// ISC, Copyright 2017 Jaco Greeff
|
||||
// @flow
|
||||
|
||||
import type { JsonRpcResponse, ProviderInterface } from '../types';
|
||||
import type { JsonRpcRequest, JsonRpcResponse, ProviderInterface } from '../types';
|
||||
|
||||
type AwaitingType = {
|
||||
callback: (error: ?Error, result: any) => void
|
||||
@@ -13,17 +13,23 @@ type WsMessageType = {
|
||||
|
||||
require('./polyfill');
|
||||
|
||||
const assert = require('@polkadot/util/assert');
|
||||
const JsonRpcCoder = require('../jsonRpcCoder');
|
||||
|
||||
module.exports = class WsProvider extends JsonRpcCoder implements ProviderInterface {
|
||||
_autoConnect: boolean = true;
|
||||
_endpoint: string;
|
||||
_handlers: { [number]: AwaitingType } = {};
|
||||
_isConnected: boolean = false;
|
||||
_queued: { [number]: JsonRpcRequest } = {};
|
||||
_websocket: WebSocket;
|
||||
|
||||
constructor (endpoint: string, autoConnect: boolean = true) {
|
||||
super();
|
||||
|
||||
assert(endpoint, 'Endpoint should be provided');
|
||||
assert(/^ws:\/\//.test(endpoint), `Endpoint should start with 'ws://', received '${endpoint}'`);
|
||||
|
||||
this._endpoint = endpoint;
|
||||
this._autoConnect = autoConnect;
|
||||
|
||||
@@ -32,9 +38,11 @@ module.exports = class WsProvider extends JsonRpcCoder implements ProviderInterf
|
||||
}
|
||||
}
|
||||
|
||||
connect = () => {
|
||||
this._handlers = {};
|
||||
get isConnected (): boolean {
|
||||
return this._isConnected;
|
||||
}
|
||||
|
||||
connect = () => {
|
||||
this._websocket = new WebSocket(this._endpoint);
|
||||
|
||||
this._websocket.onclose = this._onClose;
|
||||
@@ -45,6 +53,7 @@ module.exports = class WsProvider extends JsonRpcCoder implements ProviderInterf
|
||||
|
||||
_onClose = () => {
|
||||
// console.log('disconnected from', this._endpoint);
|
||||
this._isConnected = false;
|
||||
|
||||
if (this._autoConnect) {
|
||||
setTimeout(this.connect, 1000);
|
||||
@@ -57,6 +66,16 @@ module.exports = class WsProvider extends JsonRpcCoder implements ProviderInterf
|
||||
|
||||
_onOpen = () => {
|
||||
// console.log('connected to', this._endpoint);
|
||||
this._isConnected = true;
|
||||
|
||||
Object.keys(this._queued).forEach((id: string) => {
|
||||
try {
|
||||
this._websocket.send(this._queued[((id: any): number)]);
|
||||
delete this._queued[((id: any): number)];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_onMessage = (message: WsMessageType) => {
|
||||
@@ -82,9 +101,7 @@ module.exports = class WsProvider extends JsonRpcCoder implements ProviderInterf
|
||||
send (method: string, params: Array<any>): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this._websocket.send(
|
||||
this.encodeObject(method, params)
|
||||
);
|
||||
const encoded = this.encodeObject(method, params);
|
||||
|
||||
this._handlers[this.id] = {
|
||||
callback: (error: ?Error, result: any) => {
|
||||
@@ -95,6 +112,12 @@ module.exports = class WsProvider extends JsonRpcCoder implements ProviderInterf
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this._isConnected) {
|
||||
this._websocket.send(encoded);
|
||||
} else {
|
||||
this._queued[this.id] = encoded;
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
const { mockWs, TEST_WS_URL } = require('../../test/mockWs');
|
||||
|
||||
const { isUndefined } = require('@polkadot/util/is');
|
||||
|
||||
const Ws = require('./index');
|
||||
|
||||
let ws;
|
||||
@@ -12,10 +10,7 @@ let encodeSpy;
|
||||
let decodeSpy;
|
||||
|
||||
function createWs (requests, autoConnect) {
|
||||
mock = autoConnect || isUndefined(autoConnect)
|
||||
? mockWs(requests)
|
||||
: null;
|
||||
|
||||
mock = mockWs(requests);
|
||||
ws = new Ws(TEST_WS_URL, autoConnect);
|
||||
|
||||
encodeSpy = jest.spyOn(ws, 'encodeObject');
|
||||
@@ -26,14 +21,36 @@ function createWs (requests, autoConnect) {
|
||||
|
||||
describe('Ws', () => {
|
||||
afterEach(() => {
|
||||
encodeSpy.mockRestore();
|
||||
decodeSpy.mockRestore();
|
||||
if (encodeSpy) {
|
||||
encodeSpy.mockRestore();
|
||||
encodeSpy = null;
|
||||
}
|
||||
|
||||
if (decodeSpy) {
|
||||
decodeSpy.mockRestore();
|
||||
decodeSpy = null;
|
||||
}
|
||||
|
||||
if (mock) {
|
||||
mock.done();
|
||||
mock = null;
|
||||
}
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('requires the endpoint to be set', () => {
|
||||
expect(
|
||||
() => new Ws()
|
||||
).toThrow(/should be provided/);
|
||||
});
|
||||
|
||||
it('requires an ws:// prefixed endpoint', () => {
|
||||
expect(
|
||||
() => new Ws('http://')
|
||||
).toThrow(/with 'ws/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('websocket', () => {
|
||||
let errorSpy;
|
||||
|
||||
@@ -74,6 +91,10 @@ describe('Ws', () => {
|
||||
expect(ws.connect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sets isConnected false', () => {
|
||||
expect(ws.isConnected).toEqual(false);
|
||||
});
|
||||
|
||||
it('does not reconnect when autoConnect false', () => {
|
||||
mock.done();
|
||||
ws = createWs([], false);
|
||||
@@ -108,7 +129,10 @@ describe('Ws', () => {
|
||||
|
||||
describe('send', () => {
|
||||
it('handles internal errors', () => {
|
||||
ws = createWs([], false);
|
||||
ws = createWs([]);
|
||||
|
||||
ws._isConnected = true;
|
||||
ws._websocket = null;
|
||||
|
||||
return ws
|
||||
.send('test_encoding', ['param'])
|
||||
@@ -190,4 +214,35 @@ describe('Ws', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('queued', () => {
|
||||
let sendPromise;
|
||||
|
||||
beforeEach(() => {
|
||||
ws = createWs([{
|
||||
id: 1,
|
||||
method: 'test_queue',
|
||||
reply: {
|
||||
result: 'ok'
|
||||
}
|
||||
}], false);
|
||||
sendPromise = ws.send('test_queue', []);
|
||||
});
|
||||
|
||||
it('sends messages when connected', () => {
|
||||
ws.connect();
|
||||
|
||||
return sendPromise.then((result) => {
|
||||
expect(result).toEqual('ok');
|
||||
});
|
||||
});
|
||||
|
||||
it('sends messages when connected (errors)', () => {
|
||||
ws.connect();
|
||||
|
||||
return sendPromise.then((result) => {
|
||||
expect(result).toEqual('ok');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
// @flow
|
||||
|
||||
if (typeof WebSocket === 'undefined') {
|
||||
global.WebSocket = require('ws');
|
||||
global.WebSocket = require('websocket').w3cwebsocket;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ describe('ws/polyfill', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
origWs = global.WebSocket;
|
||||
global.fetch = null;
|
||||
global.WebSocket = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
Reference in New Issue
Block a user