| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- const test = require('ava');
- const {spawn} = require('child_process');
- const WebSocket = require('ws');
- const WebSocketServer = require('ws').Server;
- const ReconnectingWebSocket = require('..');
- const PORT = 50123;
- const PORT_UNRESPONSIVE = 50124;
- const URL = `ws://localhost:${PORT}`;
- test.beforeEach(() => {
- global.WebSocket = WebSocket;
- });
- test.afterEach(() => {
- delete global.WebSocket;
- });
- test('throws with invalid constructor', t => {
- delete global.WebSocket;
- t.throws(() => {
- new ReconnectingWebSocket(URL, undefined, {WebSocket: 123});
- });
- });
- test('throws with missing constructor', t => {
- delete global.WebSocket;
- t.throws(() => {
- new ReconnectingWebSocket(URL, undefined);
- });
- });
- test('throws if not created with `new`', t => {
- t.throws(() => {
- ReconnectingWebSocket(URL, undefined);
- }, TypeError);
- });
- test.cb('global WebSocket is used if available', t => {
- const ws = new ReconnectingWebSocket(URL, undefined, {
- maxRetries: 0,
- });
- ws.onerror = () => {
- t.true(ws._ws instanceof WebSocket);
- t.end();
- };
- });
- test.cb('send is ignored when not ready', t => {
- const ws = new ReconnectingWebSocket(URL, undefined, {
- maxRetries: 0,
- });
- ws.send('message');
- ws.onerror = () => {
- t.pass();
- t.end();
- };
- });
- test.cb('getters when not ready', t => {
- const ws = new ReconnectingWebSocket(URL, undefined, {
- maxRetries: 0,
- });
- t.is(ws.bufferedAmount, 0);
- t.is(ws.protocol, '');
- t.is(ws.url, '');
- t.is(ws.extensions, '');
- t.is(ws.binaryType, 'blob');
- ws.onerror = () => {
- t.pass();
- t.end();
- };
- });
- test.cb('debug', t => {
- const log = console.log;
- console.log = () => t.pass();
- const ws = new ReconnectingWebSocket(URL, undefined, {
- maxRetries: 0,
- debug: true,
- });
- ws.onerror = () => {
- ws._options.debug = false;
- console.log = log;
- t.end();
- };
- });
- test.cb('pass WebSocket via options', t => {
- delete global.WebSocket;
- const ws = new ReconnectingWebSocket(URL, undefined, {
- WebSocket,
- maxRetries: 0,
- });
- ws.onerror = () => {
- t.true(ws._ws instanceof WebSocket);
- t.end();
- };
- });
- test('URL provider', async t => {
- const url = 'example.com';
- const ws = new ReconnectingWebSocket(URL, undefined, {
- maxRetries: 0,
- });
- t.is(await ws._getNextUrl(url), url, 'string');
- t.is(await ws._getNextUrl(() => url), url, '() -> string');
- t.is(await ws._getNextUrl(() => Promise.resolve(url)), url, '() -> Promise<string>');
- try {
- await ws._getNextUrl(123);
- t.fail();
- } catch (e) {
- t.pass();
- }
- try {
- await ws._getNextUrl(() => 123);
- t.fail();
- } catch (e) {
- t.pass();
- }
- });
- test('connection status constants', t => {
- const ws = new ReconnectingWebSocket(URL, null, {maxRetries: 0});
- t.is(ReconnectingWebSocket.CONNECTING, 0);
- t.is(ReconnectingWebSocket.OPEN, 1);
- t.is(ReconnectingWebSocket.CLOSING, 2);
- t.is(ReconnectingWebSocket.CLOSED, 3);
- t.is(ws.CONNECTING, 0);
- t.is(ws.OPEN, 1);
- t.is(ws.CLOSING, 2);
- t.is(ws.CLOSED, 3);
- ws.close();
- });
- const maxRetriesTest = (count, t) => {
- const ws = new ReconnectingWebSocket(URL, null, {
- maxRetries: count,
- maxReconnectionDelay: 200,
- });
- t.plan(count + 1);
- ws.addEventListener('error', event => {
- t.pass();
- if (ws.retryCount === count) {
- setTimeout(() => t.end(), 500);
- }
- if (ws.retryCount > count) {
- t.fail(`too many retries: ${ws.retryCount}`);
- }
- });
- };
- test.cb('max retries: 0', t => maxRetriesTest(0, t));
- test.cb('max retries: 1', t => maxRetriesTest(1, t));
- test.cb('max retries: 5', t => maxRetriesTest(5, t));
- test.cb('level0 event listeners are kept after reconnect', t => {
- const ws = new ReconnectingWebSocket(URL, null, {
- maxRetries: 4,
- reconnectionDelayFactor: 1.2,
- maxReconnectionDelay: 20,
- minReconnectionDelay: 10,
- });
- const handleOpen = () => {};
- const handleClose = () => {};
- const handleMessage = () => {};
- const handleError = () => {
- t.is(ws.onopen, handleOpen);
- t.is(ws.onclose, handleClose);
- t.is(ws.onmessage, handleMessage);
- t.is(ws.onerror, handleError);
- if (ws.retryCount === 4) {
- t.end();
- }
- };
- ws.onopen = handleOpen;
- ws.onclose = handleClose;
- ws.onmessage = handleMessage;
- ws.onerror = handleError;
- });
- test.cb('level2 event listeners', t => {
- const anyProtocol = 'foobar';
- const wss = new WebSocketServer({port: PORT});
- const ws = new ReconnectingWebSocket(URL, anyProtocol, {});
- ws.addEventListener('open', () => {
- t.is(ws.protocol, anyProtocol);
- t.is(ws.extensions, '');
- t.is(ws.bufferedAmount, 0);
- ws.close();
- });
- const fail = () => {
- t.fail();
- };
- ws.addEventListener('unknown1', fail);
- ws.addEventListener('open', fail);
- ws.addEventListener('open', fail);
- ws.removeEventListener('open', fail);
- ws.removeEventListener('unknown2', fail);
- ws.addEventListener('close', () => {
- wss.close();
- setTimeout(() => t.end(), 500);
- });
- });
- test.cb('connection timeout', t => {
- const proc = spawn('node', [`${__dirname}/unresponsive-server.js`, PORT_UNRESPONSIVE, 5000]);
- t.plan(2);
- let lock = false;
- proc.stdout.on('data', d => {
- if (lock) return;
- lock = true;
- const ws = new ReconnectingWebSocket(`ws://localhost:${PORT_UNRESPONSIVE}`, null, {
- minReconnectionDelay: 50,
- connectionTimeout: 500,
- maxRetries: 1,
- });
- ws.addEventListener('error', event => {
- t.is(event.message, 'TIMEOUT');
- if (ws.retryCount === 1) {
- setTimeout(() => t.end(), 1000);
- }
- });
- ws.addEventListener('close', event => {
- console.log('>>>> CLOSE', event.message);
- });
- });
- });
- test.cb('getters', t => {
- const anyProtocol = 'foobar';
- const wss = new WebSocketServer({port: PORT});
- const ws = new ReconnectingWebSocket(URL, anyProtocol, {maxReconnectionDelay: 100});
- ws.addEventListener('open', () => {
- t.is(ws.protocol, anyProtocol);
- t.is(ws.extensions, '');
- t.is(ws.bufferedAmount, 0);
- t.is(ws.binaryType, 'nodebuffer');
- ws.close();
- });
- ws.addEventListener('close', () => {
- wss.close();
- setTimeout(() => t.end(), 500);
- });
- });
- test.cb('binaryType', t => {
- const wss = new WebSocketServer({port: PORT});
- const ws = new ReconnectingWebSocket(URL, undefined, {minReconnectionDelay: 0});
- t.is(ws.binaryType, 'blob');
- ws.binaryType = 'arraybuffer';
- ws.addEventListener('open', () => {
- t.is(ws.binaryType, 'arraybuffer', 'assigned after open');
- ws.binaryType = 'nodebuffer';
- t.is(ws.binaryType, 'nodebuffer');
- ws.close();
- });
- ws.addEventListener('close', () => {
- wss.close();
- setTimeout(() => t.end(), 500);
- });
- });
- test.cb('calling to close multiple times', t => {
- const wss = new WebSocketServer({port: PORT});
- const ws = new ReconnectingWebSocket(URL, undefined, {});
- ws.addEventListener('open', () => {
- ws.close();
- ws.close();
- ws.close();
- });
- ws.addEventListener('close', () => {
- wss.close();
- setTimeout(() => t.end(), 500);
- });
- });
- test.cb('calling to reconnect when not ready', t => {
- const wss = new WebSocketServer({port: PORT});
- const ws = new ReconnectingWebSocket(URL, undefined, {});
- ws.reconnect();
- ws.reconnect();
- ws.addEventListener('open', () => {
- ws.close();
- });
- ws.addEventListener('close', () => {
- wss.close();
- setTimeout(() => t.end(), 500);
- });
- });
- test.cb('connect, send, receive, close', t => {
- const anyMessageText = 'hello';
- const anyProtocol = 'foobar';
- const wss = new WebSocketServer({port: PORT});
- wss.on('connection', ws => {
- ws.on('message', msg => {
- ws.send(msg);
- });
- });
- wss.on('error', () => {
- t.fail();
- });
- t.plan(7);
- const ws = new ReconnectingWebSocket(URL, anyProtocol, {
- minReconnectionDelay: 100,
- maxReconnectionDelay: 200,
- });
- t.is(ws.readyState, ws.CONNECTING);
- ws.addEventListener('open', () => {
- t.is(ws.protocol, anyProtocol);
- t.is(ws.readyState, ws.OPEN);
- ws.send(anyMessageText);
- });
- ws.addEventListener('message', msg => {
- t.is(msg.data, anyMessageText);
- ws.close(1000, '');
- t.is(ws.readyState, ws.CLOSING);
- });
- ws.addEventListener('close', () => {
- t.is(ws.readyState, ws.CLOSED);
- t.is(ws.url, URL);
- wss.close();
- setTimeout(() => t.end(), 1000);
- });
- });
- test.cb('connect, send, receive, reconnect', t => {
- const anyMessageText = 'hello';
- const anyProtocol = 'foobar';
- const wss = new WebSocketServer({port: PORT});
- wss.on('connection', ws => {
- ws.on('message', msg => {
- ws.send(msg);
- });
- });
- const totalRounds = 3;
- let currentRound = 0;
- // 6 = 3 * 2 open
- // 8 = 2 * 3 message + 2 reconnect
- // 7 = 2 * 3 close + 1 closed
- t.plan(21);
- const ws = new ReconnectingWebSocket(URL, anyProtocol, {
- minReconnectionDelay: 100,
- maxReconnectionDelay: 200,
- });
- ws.onopen = () => {
- currentRound++;
- t.is(ws.protocol, anyProtocol);
- t.is(ws.readyState, ws.OPEN);
- ws.send(anyMessageText);
- };
- ws.onmessage = msg => {
- t.is(msg.data, anyMessageText);
- if (currentRound < totalRounds) {
- ws.reconnect(1000, 'reconnect');
- t.is(ws.retryCount, 0);
- } else {
- ws.close(1000, 'close');
- }
- t.is(ws.readyState, ws.CLOSING);
- };
- ws.addEventListener('close', event => {
- t.is(ws.url, URL);
- if (currentRound >= totalRounds) {
- t.is(ws.readyState, ws.CLOSED);
- wss.close();
- setTimeout(() => t.end(), 1000);
- t.is(event.reason, 'close');
- } else {
- t.is(event.reason, 'reconnect');
- }
- });
- });
- test.cb('immediatly-failed connection should not timeout', t => {
- const ws = new ReconnectingWebSocket('ws://thiswillfail.com', null, {
- maxRetries: 2,
- connectionTimeout: 500,
- });
- ws.addEventListener('error', err => {
- if (err.message === 'TIMEOUT') {
- t.fail();
- }
- if (ws.retryCount === 2) {
- setTimeout(() => t.end(), 1500);
- }
- if (ws.retryCount > 2) {
- t.fail();
- }
- });
- });
|