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:
Jaco Greeff
2017-12-03 17:13:19 +01:00
committed by GitHub
parent 42fa4a0eeb
commit 5b2630a013
10 changed files with 150 additions and 38 deletions
+7
View File
@@ -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, {
+24 -1
View File
@@ -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', () => {
+1
View File
@@ -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>;
}
+29 -6
View File
@@ -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);
}
+64 -9
View File
@@ -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');
});
});
});
});
+1 -1
View File
@@ -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(() => {