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
+1 -1
View File
@@ -1,5 +1,5 @@
{
"version": "0.5.12",
"version": "0.6.0",
"private": true,
"workspaces": [
"packages/*"
+1 -1
View File
@@ -37,6 +37,6 @@
"@polkadot/util": "^0.6.7",
"babel-runtime": "^6.26.0",
"isomorphic-fetch": "^2.2.1",
"ws": "^3.3.2"
"websocket": "^1.0.25"
}
}
+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(() => {
+21 -18
View File
@@ -305,10 +305,6 @@ async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
async-limiter@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
async@^1.4.0, async@^1.5.0:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -2791,7 +2787,7 @@ is-text-path@^1.0.0:
dependencies:
text-extensions "^1.0.0"
is-typedarray@~1.0.0:
is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -3618,7 +3614,7 @@ mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
nan@^2.2.1, nan@^2.3.0:
nan@^2.2.1, nan@^2.3.0, nan@^2.3.3:
version "2.8.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
@@ -5025,6 +5021,12 @@ type-detect@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2"
typedarray-to-buffer@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz#1017b32d984ff556eba100f501589aba1ace2e04"
dependencies:
is-typedarray "^1.0.0"
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@@ -5054,10 +5056,6 @@ uid-number@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
ultron@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
unherit@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.0.tgz#6b9aaedfbf73df1756ad9e316dd981885840cd7d"
@@ -5218,6 +5216,15 @@ webidl-conversions@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
websocket@^1.0.25:
version "1.0.25"
resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.25.tgz#998ec790f0a3eacb8b08b50a4350026692a11958"
dependencies:
debug "^2.2.0"
nan "^2.3.3"
typedarray-to-buffer "^3.1.2"
yaeti "^0.0.6"
whatwg-encoding@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3"
@@ -5317,14 +5324,6 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
ws@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.2.tgz#96c1d08b3fefda1d5c1e33700d3bfaa9be2d5608"
dependencies:
async-limiter "~1.0.0"
safe-buffer "~5.1.0"
ultron "~1.1.0"
x-is-function@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/x-is-function/-/x-is-function-1.0.4.tgz#5d294dc3d268cbdd062580e0c5df77a391d1fa1e"
@@ -5345,6 +5344,10 @@ y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
yaeti@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"