/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$" }] */
'use strict' ;
const assert = require('assert' );
const crypto = require('crypto' );
const https = require('https' );
const http = require('http' );
const path = require('path' );
const net = require('net' );
const tls = require('tls' );
const os = require('os' );
const fs = require('fs' );
const { URL } = require('url' );
const Sender = require('../lib/sender' );
const WebSocket = require('..' );
const {
CloseEvent,
ErrorEvent,
Event,
MessageEvent
} = require('../lib/event-target' );
const { EMPTY_BUFFER, GUID, kListener, NOOP } = require('../lib/constants' );
class CustomAgent extends http.Agent {
addRequest() {}
}
describe('WebSocket' , () => {
describe('#ctor' , () => {
it('throws an error when using an invalid url' , () => {
assert .throws (
() => new WebSocket('foo' ),
/^SyntaxError: Invalid URL: foo$/
);
assert .throws (
() => new WebSocket('https://websocket-echo.com '),
/^SyntaxError: The URL's protocol must be one of "ws:", "wss:", or "ws\+unix:"$/
);
assert .throws (
() => new WebSocket('ws+unix:' ),
/^SyntaxError: The URL's pathname is empty$/
);
assert .throws (
() => new WebSocket('wss://websocket-echo.com#foo'),
/^SyntaxError: The URL contains a fragment identifier$/
);
});
it('throws an error if a subprotocol is invalid or duplicated' , () => {
for (const subprotocol of [null , '' , 'a,b' , ['a' , 'a' ]]) {
assert .throws (
() => new WebSocket('ws://localhost', subprotocol),
/^SyntaxError: An invalid or duplicated subprotocol was specified$/
);
}
});
it('accepts `url.URL` objects as url' , (done) => {
const agent = new CustomAgent();
agent.addRequest = (req, opts) => {
assert .strictEqual(opts.host, '::1' );
assert .strictEqual(req.path, '/' );
done();
};
const ws = new WebSocket(new URL('ws://[::1]'), { agent });
});
describe('options' , () => {
it('accepts the `options` object as 3rd argument' , () => {
const agent = new CustomAgent();
let count = 0;
let ws;
agent.addRequest = (req) => {
assert .strictEqual(
req.getHeader('sec-websocket-protocol' ),
undefined
);
count++;
};
ws = new WebSocket('ws://localhost', undefined, { agent });
ws = new WebSocket('ws://localhost', [], { agent });
assert .strictEqual(count, 2);
});
it('accepts the `maxPayload` option' , (done) => {
const maxPayload = 20480;
const wss = new WebSocket.Server(
{
perMessageDeflate: true ,
port: 0
},
() => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
perMessageDeflate: true ,
maxPayload
});
ws.on('open' , () => {
assert .strictEqual(ws._receiver._maxPayload, maxPayload);
assert .strictEqual(
ws._receiver._extensions['permessage-deflate' ]._maxPayload,
maxPayload
);
wss.close(done);
});
}
);
wss.on('connection' , (ws) => {
ws.close();
});
});
it('throws an error when using an invalid `protocolVersion`' , () => {
const options = { agent: new CustomAgent(), protocolVersion: 1000 };
assert .throws (
() => new WebSocket('ws://localhost', options),
/^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/
);
});
it('honors the `generateMask` option' , (done) => {
const data = Buffer.from('foo' );
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
generateMask() {}
});
ws.on('open' , () => {
ws.send(data);
});
ws.on('close' , (code, reason) => {
assert .strictEqual(code, 1005);
assert .deepStrictEqual(reason, EMPTY_BUFFER);
wss.close(done);
});
});
wss.on('connection' , (ws) => {
const chunks = [];
ws._socket.prependListener('data' , (chunk) => {
chunks.push(chunk);
});
ws.on('message' , (message) => {
assert .deepStrictEqual(message, data);
assert .deepStrictEqual(
Buffer.concat(chunks).slice(2, 6),
Buffer.alloc(4)
);
ws.close();
});
});
});
});
});
describe('Constants' , () => {
const readyStates = {
CONNECTING: 0,
OPEN: 1,
CLOSING: 2,
CLOSED: 3
};
Object.keys(readyStates).forEach((state) => {
describe(`\`${state}\``, () => {
it('is enumerable property of class' , () => {
const descriptor = Object.getOwnPropertyDescriptor(WebSocket, state);
assert .deepStrictEqual(descriptor, {
configurable: false ,
enumerable: true ,
value: readyStates[state],
writable: false
});
});
it('is enumerable property of prototype' , () => {
const descriptor = Object.getOwnPropertyDescriptor(
WebSocket.prototype,
state
);
assert .deepStrictEqual(descriptor, {
configurable: false ,
enumerable: true ,
value: readyStates[state],
writable: false
});
});
});
});
});
describe('Attributes' , () => {
describe('`binaryType`' , () => {
it('is enumerable and configurable' , () => {
const descriptor = Object.getOwnPropertyDescriptor(
WebSocket.prototype,
'binaryType'
);
assert .strictEqual(descriptor.configurable, true );
assert .strictEqual(descriptor.enumerable, true );
assert .ok(descriptor.get !== undefined);
assert .ok(descriptor.set !== undefined);
});
it("defaults to 'nodebuffer'" , () => {
const ws = new WebSocket('ws://localhost', {
agent: new CustomAgent()
});
assert .strictEqual(ws.binaryType, 'nodebuffer' );
});
it("can be changed to 'arraybuffer' or 'fragments'" , () => {
const ws = new WebSocket('ws://localhost', {
agent: new CustomAgent()
});
ws.binaryType = 'arraybuffer' ;
assert .strictEqual(ws.binaryType, 'arraybuffer' );
ws.binaryType = 'foo' ;
assert .strictEqual(ws.binaryType, 'arraybuffer' );
ws.binaryType = 'fragments' ;
assert .strictEqual(ws.binaryType, 'fragments' );
ws.binaryType = '' ;
assert .strictEqual(ws.binaryType, 'fragments' );
ws.binaryType = 'nodebuffer' ;
assert .strictEqual(ws.binaryType, 'nodebuffer' );
});
});
describe('`bufferedAmount`' , () => {
it('is enumerable and configurable' , () => {
const descriptor = Object.getOwnPropertyDescriptor(
WebSocket.prototype,
'bufferedAmount'
);
assert .strictEqual(descriptor.configurable, true );
assert .strictEqual(descriptor.enumerable, true );
assert .ok(descriptor.get !== undefined);
assert .ok(descriptor.set === undefined);
});
it('defaults to zero' , () => {
const ws = new WebSocket('ws://localhost', {
agent: new CustomAgent()
});
assert .strictEqual(ws.bufferedAmount, 0);
});
it('defaults to zero upon "open"' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.onopen = () => {
assert .strictEqual(ws.bufferedAmount, 0);
wss.close(done);
};
});
wss.on('connection' , (ws) => {
ws.close();
});
});
it('takes into account the data in the sender queue' , (done) => {
const wss = new WebSocket.Server(
{
perMessageDeflate: true ,
port: 0
},
() => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
perMessageDeflate: { threshold: 0 }
});
ws.on('open' , () => {
ws.send('foo' );
assert .strictEqual(ws.bufferedAmount, 3);
ws.send('bar' , (err) => {
assert .ifError(err);
assert .strictEqual(ws.bufferedAmount, 0);
wss.close(done);
});
assert .strictEqual(ws.bufferedAmount, 6);
});
}
);
wss.on('connection' , (ws) => {
ws.close();
});
});
it('takes into account the data in the socket queue' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
});
wss.on('connection' , (ws) => {
const data = Buffer.alloc(1024, 61);
while (ws.bufferedAmount === 0) {
ws.send(data);
}
assert .ok(ws.bufferedAmount > 0);
assert .strictEqual(
ws.bufferedAmount,
ws._socket._writableState.length
);
ws.on('close' , () => wss.close(done));
ws.close();
});
});
});
describe('`extensions`' , () => {
it('is enumerable and configurable' , () => {
const descriptor = Object.getOwnPropertyDescriptor(
WebSocket.prototype,
'bufferedAmount'
);
assert .strictEqual(descriptor.configurable, true );
assert .strictEqual(descriptor.enumerable, true );
assert .ok(descriptor.get !== undefined);
assert .ok(descriptor.set === undefined);
});
it('exposes the negotiated extensions names (1/2)' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
assert .strictEqual(ws.extensions, '' );
ws.on('open' , () => {
assert .strictEqual(ws.extensions, '' );
ws.on('close' , () => wss.close(done));
});
});
wss.on('connection' , (ws) => {
assert .strictEqual(ws.extensions, '' );
ws.close();
});
});
it('exposes the negotiated extensions names (2/2)' , (done) => {
const wss = new WebSocket.Server(
{
perMessageDeflate: true ,
port: 0
},
() => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
assert .strictEqual(ws.extensions, '' );
ws.on('open' , () => {
assert .strictEqual(ws.extensions, 'permessage-deflate' );
ws.on('close' , () => wss.close(done));
});
}
);
wss.on('connection' , (ws) => {
assert .strictEqual(ws.extensions, 'permessage-deflate' );
ws.close();
});
});
});
describe('`isPaused`' , () => {
it('is enumerable and configurable' , () => {
const descriptor = Object.getOwnPropertyDescriptor(
WebSocket.prototype,
'isPaused'
);
assert .strictEqual(descriptor.configurable, true );
assert .strictEqual(descriptor.enumerable, true );
assert .ok(descriptor.get !== undefined);
assert .ok(descriptor.set === undefined);
});
it('indicates whether the websocket is paused' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('open' , () => {
ws.pause();
assert .ok(ws.isPaused);
ws.resume();
assert .ok(!ws.isPaused);
ws.close();
wss.close(done);
});
assert .ok(!ws.isPaused);
});
});
});
describe('`protocol`' , () => {
it('is enumerable and configurable' , () => {
const descriptor = Object.getOwnPropertyDescriptor(
WebSocket.prototype,
'protocol'
);
assert .strictEqual(descriptor.configurable, true );
assert .strictEqual(descriptor.enumerable, true );
assert .ok(descriptor.get !== undefined);
assert .ok(descriptor.set === undefined);
});
it('exposes the subprotocol selected by the server' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const port = wss.address().port;
const ws = new WebSocket(`ws://localhost:${port}`, 'foo');
assert .strictEqual(ws.extensions, '' );
ws.on('open' , () => {
assert .strictEqual(ws.protocol, 'foo' );
ws.on('close' , () => wss.close(done));
});
});
wss.on('connection' , (ws) => {
assert .strictEqual(ws.protocol, 'foo' );
ws.close();
});
});
});
describe('`readyState`' , () => {
it('is enumerable and configurable' , () => {
const descriptor = Object.getOwnPropertyDescriptor(
WebSocket.prototype,
'readyState'
);
assert .strictEqual(descriptor.configurable, true );
assert .strictEqual(descriptor.enumerable, true );
assert .ok(descriptor.get !== undefined);
assert .ok(descriptor.set === undefined);
});
it('defaults to `CONNECTING`' , () => {
const ws = new WebSocket('ws://localhost', {
agent: new CustomAgent()
});
assert .strictEqual(ws.readyState, WebSocket.CONNECTING);
});
it('is set to `OPEN` once connection is established' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('open' , () => {
assert .strictEqual(ws.readyState, WebSocket.OPEN);
ws.close();
});
ws.on('close' , () => wss.close(done));
});
});
it('is set to `CLOSED` once connection is closed' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('close' , () => {
assert .strictEqual(ws.readyState, WebSocket.CLOSED);
wss.close(done);
});
ws.on('open' , () => ws.close(1001));
});
});
it('is set to `CLOSED` once connection is terminated' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('close' , () => {
assert .strictEqual(ws.readyState, WebSocket.CLOSED);
wss.close(done);
});
ws.on('open' , () => ws.terminate());
});
});
});
describe('`url`' , () => {
it('is enumerable and configurable' , () => {
const descriptor = Object.getOwnPropertyDescriptor(
WebSocket.prototype,
'url'
);
assert .strictEqual(descriptor.configurable, true );
assert .strictEqual(descriptor.enumerable, true );
assert .ok(descriptor.get !== undefined);
assert .ok(descriptor.set === undefined);
});
it('exposes the server url' , () => {
const url = 'ws://localhost';
const ws = new WebSocket(url, { agent: new CustomAgent() });
assert .strictEqual(ws.url, url);
});
});
});
describe('Events' , () => {
it("emits an 'error' event if an error occurs" , (done) => {
let clientCloseEventEmitted = false ;
let serverClientCloseEventEmitted = false ;
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('error' , (err) => {
assert .ok(err instanceof RangeError);
assert .strictEqual(err.code, 'WS_ERR_INVALID_OPCODE' );
assert .strictEqual(
err.message,
'Invalid WebSocket frame: invalid opcode 5'
);
ws.on('close' , (code, reason) => {
assert .strictEqual(code, 1006);
assert .strictEqual(reason, EMPTY_BUFFER);
clientCloseEventEmitted = true ;
if (serverClientCloseEventEmitted) wss.close(done);
});
});
});
wss.on('connection' , (ws) => {
ws.on('close' , (code, reason) => {
assert .strictEqual(code, 1002);
assert .deepStrictEqual(reason, EMPTY_BUFFER);
serverClientCloseEventEmitted = true ;
if (clientCloseEventEmitted) wss.close(done);
});
ws._socket.write(Buffer.from([0x85, 0x00]));
});
});
it('does not re-emit `net.Socket` errors' , (done) => {
const codes = ['EPIPE' , 'ECONNABORTED' , 'ECANCELED' , 'ECONNRESET' ];
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('open' , () => {
ws._socket.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .ok(codes.includes(err.code), `Unexpected code: ${err.code}`);
ws.on('close' , (code, message) => {
assert .strictEqual(code, 1006);
assert .strictEqual(message, EMPTY_BUFFER);
wss.close(done);
});
});
for (const client of wss.clients) client.terminate();
ws.send('foo' );
ws.send('bar' );
});
});
});
it("emits an 'upgrade' event" , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('upgrade' , (res) => {
assert .ok(res instanceof http.IncomingMessage);
wss.close(done);
});
});
wss.on('connection' , (ws) => {
ws.close();
});
});
it("emits a 'ping' event" , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('ping' , () => wss.close(done));
});
wss.on('connection' , (ws) => {
ws.ping();
ws.close();
});
});
it("emits a 'pong' event" , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('pong' , () => wss.close(done));
});
wss.on('connection' , (ws) => {
ws.pong();
ws.close();
});
});
it("emits a 'redirect' event" , (done) => {
const server = http.createServer();
const wss = new WebSocket.Server({ noServer: true , path: '/foo' });
server.once('upgrade' , (req, socket) => {
socket.end('HTTP/1.1 302 Found\r\nLocation: /foo\r\n\r\n' );
server.once('upgrade' , (req, socket, head) => {
wss.handleUpgrade(req, socket, head, (ws) => {
ws.close();
});
});
});
server.listen(() => {
const port = server.address().port;
const ws = new WebSocket(`ws://localhost:${port}`, {
followRedirects: true
});
ws.on('redirect' , (url, req) => {
assert .strictEqual(ws._redirects, 1);
assert .strictEqual(url, `ws://localhost:${port}/foo`);
assert .ok(req instanceof http.ClientRequest);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
server.close(done);
});
});
});
});
});
describe('Connection establishing' , () => {
const server = http.createServer();
beforeEach((done) => server.listen(0, done));
afterEach((done) => server.close(done));
it('fails if the Upgrade header field value is not "websocket"' , (done) => {
server.once('upgrade' , (req, socket) => {
socket.on('end' , socket.end);
socket.write(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Connection: Upgrade\r\n' +
'Upgrade: foo\r\n' +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'Invalid Upgrade header' );
done();
});
});
it('fails if the Sec-WebSocket-Accept header is invalid' , (done) => {
server.once('upgrade' , (req, socket) => {
socket.on('end' , socket.end);
socket.write(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
'Sec-WebSocket-Accept: CxYS6+NgJSBG74mdgLvGscRvpns=\r\n' +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'Invalid Sec-WebSocket-Accept header' );
done();
});
});
it('close event is raised when server closes connection' , (done) => {
server.once('upgrade' , (req, socket) => {
const key = crypto
.createHash('sha1' )
.update(req.headers['sec-websocket-key' ] + GUID)
.digest('base64' );
socket.end(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
`Sec-WebSocket-Accept: ${key}\r\n` +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('close' , (code, reason) => {
assert .strictEqual(code, 1006);
assert .strictEqual(reason, EMPTY_BUFFER);
done();
});
});
it('error is emitted if server aborts connection' , (done) => {
server.once('upgrade' , (req, socket) => {
socket.end(
`HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
'Connection: close\r\n' +
'Content-type: text/html\r\n' +
`Content-Length: ${http.STATUS_CODES[401].length}\r\n` +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'Unexpected server response: 401' );
done();
});
});
it('unexpected response can be read when sent by server' , (done) => {
server.once('upgrade' , (req, socket) => {
socket.end(
`HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
'Connection: close\r\n' +
'Content-type: text/html\r\n' +
'Content-Length: 3\r\n' +
'\r\n' +
'foo'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , () => done(new Error("Unexpected 'error' event" )));
ws.on('unexpected-response' , (req, res) => {
assert .strictEqual(res.statusCode, 401);
let data = '' ;
res.on('data' , (v) => {
data += v;
});
res.on('end' , () => {
assert .strictEqual(data, 'foo' );
done();
});
});
});
it('request can be aborted when unexpected response is sent by server' , (done) => {
server.once('upgrade' , (req, socket) => {
socket.end(
`HTTP/1.1 401 ${http.STATUS_CODES[401]}\r\n` +
'Connection: close\r\n' +
'Content-type: text/html\r\n' +
'Content-Length: 3\r\n' +
'\r\n' +
'foo'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , () => done(new Error("Unexpected 'error' event" )));
ws.on('unexpected-response' , (req, res) => {
assert .strictEqual(res.statusCode, 401);
res.on('end' , done);
req.abort();
});
});
it('fails if the opening handshake timeout expires' , (done) => {
server.once('upgrade' , (req, socket) => socket.on('end' , socket.end));
const port = server.address().port;
const ws = new WebSocket(`ws://localhost:${port}`, {
handshakeTimeout: 100
});
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'Opening handshake has timed out' );
done();
});
});
it('fails if an unexpected Sec-WebSocket-Extensions header is received' , (done) => {
server.once('upgrade' , (req, socket) => {
const key = crypto
.createHash('sha1' )
.update(req.headers['sec-websocket-key' ] + GUID)
.digest('base64' );
socket.end(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
`Sec-WebSocket-Accept: ${key}\r\n` +
'Sec-WebSocket-Extensions: foo\r\n' +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`, {
perMessageDeflate: false
});
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(
err.message,
'Server sent a Sec-WebSocket-Extensions header but no extension ' +
'was requested'
);
ws.on('close' , () => done());
});
});
it('fails if the Sec-WebSocket-Extensions header is invalid (1/2)' , (done) => {
server.once('upgrade' , (req, socket) => {
const key = crypto
.createHash('sha1' )
.update(req.headers['sec-websocket-key' ] + GUID)
.digest('base64' );
socket.end(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
`Sec-WebSocket-Accept: ${key}\r\n` +
'Sec-WebSocket-Extensions: foo;=\r\n' +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(
err.message,
'Invalid Sec-WebSocket-Extensions header'
);
ws.on('close' , () => done());
});
});
it('fails if the Sec-WebSocket-Extensions header is invalid (2/2)' , (done) => {
server.once('upgrade' , (req, socket) => {
const key = crypto
.createHash('sha1' )
.update(req.headers['sec-websocket-key' ] + GUID)
.digest('base64' );
socket.end(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
`Sec-WebSocket-Accept: ${key}\r\n` +
'Sec-WebSocket-Extensions: ' +
'permessage-deflate; client_max_window_bits=7\r\n' +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(
err.message,
'Invalid Sec-WebSocket-Extensions header'
);
ws.on('close' , () => done());
});
});
it('fails if an unexpected extension is received (1/2)' , (done) => {
server.once('upgrade' , (req, socket) => {
const key = crypto
.createHash('sha1' )
.update(req.headers['sec-websocket-key' ] + GUID)
.digest('base64' );
socket.end(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
`Sec-WebSocket-Accept: ${key}\r\n` +
'Sec-WebSocket-Extensions: foo\r\n' +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(
err.message,
'Server indicated an extension that was not requested'
);
ws.on('close' , () => done());
});
});
it('fails if an unexpected extension is received (2/2)' , (done) => {
server.once('upgrade' , (req, socket) => {
const key = crypto
.createHash('sha1' )
.update(req.headers['sec-websocket-key' ] + GUID)
.digest('base64' );
socket.end(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
`Sec-WebSocket-Accept: ${key}\r\n` +
'Sec-WebSocket-Extensions: permessage-deflate,foo\r\n' +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(
err.message,
'Server indicated an extension that was not requested'
);
ws.on('close' , () => done());
});
});
it('fails if server sends a subprotocol when none was requested' , (done) => {
const wss = new WebSocket.Server({ server });
wss.on('headers' , (headers) => {
headers.push('Sec-WebSocket-Protocol: foo' );
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(
err.message,
'Server sent a subprotocol but none was requested'
);
ws.on('close' , () => wss.close(done));
});
});
it('fails if server sends an invalid subprotocol (1/2)' , (done) => {
const wss = new WebSocket.Server({
handleProtocols: () => 'baz' ,
server
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`, [
'foo' ,
'bar'
]);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'Server sent an invalid subprotocol' );
ws.on('close' , () => wss.close(done));
});
});
it('fails if server sends an invalid subprotocol (2/2)' , (done) => {
server.once('upgrade' , (req, socket) => {
const key = crypto
.createHash('sha1' )
.update(req.headers['sec-websocket-key' ] + GUID)
.digest('base64' );
socket.end(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
`Sec-WebSocket-Accept: ${key}\r\n` +
'Sec-WebSocket-Protocol:\r\n' +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`, [
'foo' ,
'bar'
]);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'Server sent an invalid subprotocol' );
ws.on('close' , () => done());
});
});
it('fails if server sends no subprotocol' , (done) => {
const wss = new WebSocket.Server({
handleProtocols() {},
server
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`, [
'foo' ,
'bar'
]);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'Server sent no subprotocol' );
ws.on('close' , () => wss.close(done));
});
});
it('does not follow redirects by default' , (done) => {
server.once('upgrade' , (req, socket) => {
socket.end(
'HTTP/1.1 301 Moved Permanently\r\n' +
'Location: ws://localhost:8080\r\n' +
'\r\n'
);
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'Unexpected server response: 301' );
assert .strictEqual(ws._redirects, 0);
ws.on('close' , () => done());
});
});
it('honors the `followRedirects` option' , (done) => {
const wss = new WebSocket.Server({ noServer: true , path: '/foo' });
server.once('upgrade' , (req, socket) => {
socket.end('HTTP/1.1 302 Found\r\nLocation: /foo\r\n\r\n' );
server.once('upgrade' , (req, socket, head) => {
wss.handleUpgrade(req, socket, head, NOOP);
});
});
const port = server.address().port;
const ws = new WebSocket(`ws://localhost:${port}`, {
followRedirects: true
});
ws.on('open' , () => {
assert .strictEqual(ws.url, `ws://localhost:${port}/foo`);
assert .strictEqual(ws._redirects, 1);
ws.on('close' , () => done());
ws.close();
});
});
it('honors the `maxRedirects` option' , (done) => {
const onUpgrade = (req, socket) => {
socket.end('HTTP/1.1 302 Found\r\nLocation: /\r\n\r\n' );
};
server.on('upgrade' , onUpgrade);
const ws = new WebSocket(`ws://localhost:${server.address().port}`, {
followRedirects: true ,
maxRedirects: 1
});
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'Maximum redirects exceeded' );
assert .strictEqual(ws._redirects, 2);
server.removeListener('upgrade' , onUpgrade);
ws.on('close' , () => done());
});
});
it('emits an error if the redirect URL is invalid (1/2)' , (done) => {
server.once('upgrade' , (req, socket) => {
socket.end('HTTP/1.1 302 Found\r\nLocation: ws://\r\n\r\n');
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`, {
followRedirects: true
});
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof SyntaxError);
assert .strictEqual(err.message, 'Invalid URL: ws://');
assert .strictEqual(ws._redirects, 1);
ws.on('close' , () => done());
});
});
it('emits an error if the redirect URL is invalid (2/2)' , (done) => {
server.once('upgrade' , (req, socket) => {
socket.end('HTTP/1.1 302 Found\r\nLocation: http://localhost\r\n\r\n ');
});
const ws = new WebSocket(`ws://localhost:${server.address().port}`, {
followRedirects: true
});
ws.on('open' , () => done(new Error("Unexpected 'open' event" )));
ws.on('error' , (err) => {
assert .ok(err instanceof SyntaxError);
assert .strictEqual(
err.message,
'The URL\' s protocol must be one of "ws:" , "wss:" , or "ws+unix:" '
);
assert .strictEqual(ws._redirects, 1);
ws.on('close' , () => done());
});
});
it('uses the first url userinfo when following redirects' , (done) => {
const wss = new WebSocket.Server({ noServer: true , path: '/foo' });
const authorization = 'Basic Zm9vOmJhcg==' ;
server.once('upgrade' , (req, socket) => {
socket.end(
'HTTP/1.1 302 Found\r\n' +
`Location: ws://baz:qux@localhost:${port}/foo\r\n\r\n`
);
server.once('upgrade' , (req, socket, head) => {
wss.handleUpgrade(req, socket, head, (ws, req) => {
assert .strictEqual(req.headers.authorization, authorization);
ws.close();
});
});
});
const port = server.address().port;
const ws = new WebSocket(`ws://foo:bar@localhost:${port}`, {
followRedirects: true
});
assert .strictEqual(ws._req.getHeader('Authorization' ), authorization);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
assert .strictEqual(ws.url, `ws://baz:qux@localhost:${port}/foo`);
assert .strictEqual(ws._redirects, 1);
wss.close(done);
});
});
describe('When moving away from a secure context' , () => {
function proxy(httpServer, httpsServer) {
const server = net.createServer({ allowHalfOpen: true });
server.on('connection' , (socket) => {
socket.on('readable' , function read() {
socket.removeListener('readable' , read);
const buf = socket.read(1);
const target = buf[0] === 22 ? httpsServer : httpServer;
socket.unshift(buf);
target.emit('connection' , socket);
});
});
return server;
}
describe("If there is no 'redirect' event listener" , () => {
it('drops the `auth` option' , (done) => {
const httpServer = http.createServer();
const httpsServer = https.createServer({
cert: fs.readFileSync('test/fixtures/certificate.pem' ),
key: fs.readFileSync('test/fixtures/key.pem' )
});
const server = proxy(httpServer, httpsServer);
server.listen(() => {
const port = server.address().port;
httpsServer.on('upgrade' , (req, socket) => {
socket.on('error' , NOOP);
socket.end(
'HTTP/1.1 302 Found\r\n' +
`Location: ws://localhost:${port}/\r\n\r\n`
);
});
const wss = new WebSocket.Server({ server: httpServer });
wss.on('connection' , (ws, req) => {
assert .strictEqual(req.headers.authorization, undefined);
ws.close();
});
const ws = new WebSocket(`wss://localhost:${port}`, {
auth: 'foo:bar' ,
followRedirects: true ,
rejectUnauthorized: false
});
assert .strictEqual(
ws._req.getHeader('Authorization' ),
'Basic Zm9vOmJhcg=='
);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
assert .strictEqual(ws.url, `ws://localhost:${port}/`);
assert .strictEqual(ws._redirects, 1);
server.close(done);
});
});
});
it('drops the Authorization and Cookie headers' , (done) => {
const httpServer = http.createServer();
const httpsServer = https.createServer({
cert: fs.readFileSync('test/fixtures/certificate.pem' ),
key: fs.readFileSync('test/fixtures/key.pem' )
});
const server = proxy(httpServer, httpsServer);
server.listen(() => {
const port = server.address().port;
httpsServer.on('upgrade' , (req, socket) => {
socket.on('error' , NOOP);
socket.end(
'HTTP/1.1 302 Found\r\n' +
`Location: ws://localhost:${port}/\r\n\r\n`
);
});
const headers = {
authorization: 'Basic Zm9vOmJhcg==' ,
cookie: 'foo=bar' ,
host: 'foo'
};
const wss = new WebSocket.Server({ server: httpServer });
wss.on('connection' , (ws, req) => {
assert .strictEqual(req.headers.authorization, undefined);
assert .strictEqual(req.headers.cookie, undefined);
assert .strictEqual(req.headers.host, headers.host);
ws.close();
});
const ws = new WebSocket(`wss://localhost:${port}`, {
followRedirects: true ,
headers,
rejectUnauthorized: false
});
const firstRequest = ws._req;
assert .strictEqual(
firstRequest.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(
firstRequest.getHeader('Cookie' ),
headers.cookie
);
assert .strictEqual(firstRequest.getHeader('Host' ), headers.host);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
assert .strictEqual(ws.url, `ws://localhost:${port}/`);
assert .strictEqual(ws._redirects, 1);
server.close(done);
});
});
});
});
describe("If there is at least one 'redirect' event listener" , () => {
it('does not drop any headers by default' , (done) => {
const httpServer = http.createServer();
const httpsServer = https.createServer({
cert: fs.readFileSync('test/fixtures/certificate.pem' ),
key: fs.readFileSync('test/fixtures/key.pem' )
});
const server = proxy(httpServer, httpsServer);
server.listen(() => {
const port = server.address().port;
httpsServer.on('upgrade' , (req, socket) => {
socket.on('error' , NOOP);
socket.end(
'HTTP/1.1 302 Found\r\n' +
`Location: ws://localhost:${port}/\r\n\r\n`
);
});
const headers = {
authorization: 'Basic Zm9vOmJhcg==' ,
cookie: 'foo=bar' ,
host: 'foo'
};
const wss = new WebSocket.Server({ server: httpServer });
wss.on('connection' , (ws, req) => {
assert .strictEqual(
req.headers.authorization,
headers.authorization
);
assert .strictEqual(req.headers.cookie, headers.cookie);
assert .strictEqual(req.headers.host, headers.host);
ws.close();
});
const ws = new WebSocket(`wss://localhost:${port}`, {
followRedirects: true ,
headers,
rejectUnauthorized: false
});
const firstRequest = ws._req;
assert .strictEqual(
firstRequest.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(
firstRequest.getHeader('Cookie' ),
headers.cookie
);
assert .strictEqual(firstRequest.getHeader('Host' ), headers.host);
ws.on('redirect' , (url, req) => {
assert .strictEqual(ws._redirects, 1);
assert .strictEqual(url, `ws://localhost:${port}/`);
assert .notStrictEqual(firstRequest, req);
assert .strictEqual(
req.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(req.getHeader('Cookie' ), headers.cookie);
assert .strictEqual(req.getHeader('Host' ), headers.host);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
server.close(done);
});
});
});
});
});
});
describe('When the redirect host is different' , () => {
describe("If there is no 'redirect' event listener" , () => {
it('drops the `auth` option' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const port = wss.address().port;
server.once('upgrade' , (req, socket) => {
socket.end(
'HTTP/1.1 302 Found\r\n' +
`Location: ws://localhost:${port}/\r\n\r\n`
);
});
const ws = new WebSocket(
`ws://localhost:${server.address().port}`,
{
auth: 'foo:bar' ,
followRedirects: true
}
);
assert .strictEqual(
ws._req.getHeader('Authorization' ),
'Basic Zm9vOmJhcg=='
);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
assert .strictEqual(ws.url, `ws://localhost:${port}/`);
assert .strictEqual(ws._redirects, 1);
wss.close(done);
});
});
wss.on('connection' , (ws, req) => {
assert .strictEqual(req.headers.authorization, undefined);
ws.close();
});
});
it('drops the Authorization, Cookie and Host headers (1/4)' , (done) => {
// Test the `ws:` to `ws:` case.
const wss = new WebSocket.Server({ port: 0 }, () => {
const port = wss.address().port;
server.once('upgrade' , (req, socket) => {
socket.end(
'HTTP/1.1 302 Found\r\n' +
`Location: ws://localhost:${port}/\r\n\r\n`
);
});
const headers = {
authorization: 'Basic Zm9vOmJhcg==' ,
cookie: 'foo=bar' ,
host: 'foo'
};
const ws = new WebSocket(
`ws://localhost:${server.address().port}`,
{ followRedirects: true , headers }
);
const firstRequest = ws._req;
assert .strictEqual(
firstRequest.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(
firstRequest.getHeader('Cookie' ),
headers.cookie
);
assert .strictEqual(firstRequest.getHeader('Host' ), headers.host);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
assert .strictEqual(ws.url, `ws://localhost:${port}/`);
assert .strictEqual(ws._redirects, 1);
wss.close(done);
});
});
wss.on('connection' , (ws, req) => {
assert .strictEqual(req.headers.authorization, undefined);
assert .strictEqual(req.headers.cookie, undefined);
assert .strictEqual(
req.headers.host,
`localhost:${wss.address().port}`
);
ws.close();
});
});
it('drops the Authorization, Cookie and Host headers (2/4)' , function (done) {
if (process.platform === 'win32' ) return this .skip();
// Test the `ws:` to `ws+unix:` case.
const socketPath = path.join(
os.tmpdir(),
`ws.${crypto.randomBytes(16).toString('hex' )}.sock`
);
server.once('upgrade' , (req, socket) => {
socket.end(
`HTTP/1.1 302 Found\r\nLocation: ws+unix://${socketPath}\r\n\r\n`
);
});
const redirectedServer = http.createServer();
const wss = new WebSocket.Server({ server: redirectedServer });
wss.on('connection' , (ws, req) => {
assert .strictEqual(req.headers.authorization, undefined);
assert .strictEqual(req.headers.cookie, undefined);
assert .strictEqual(req.headers.host, 'localhost' );
ws.close();
});
redirectedServer.listen(socketPath, () => {
const headers = {
authorization: 'Basic Zm9vOmJhcg==' ,
cookie: 'foo=bar' ,
host: 'foo'
};
const ws = new WebSocket(
`ws://localhost:${server.address().port}`,
{ followRedirects: true , headers }
);
const firstRequest = ws._req;
assert .strictEqual(
firstRequest.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(
firstRequest.getHeader('Cookie' ),
headers.cookie
);
assert .strictEqual(firstRequest.getHeader('Host' ), headers.host);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
assert .strictEqual(ws.url, `ws+unix://${socketPath}`);
assert .strictEqual(ws._redirects, 1);
redirectedServer.close(done);
});
});
});
it('drops the Authorization, Cookie and Host headers (3/4)' , function (done) {
if (process.platform === 'win32' ) return this .skip();
// Test the `ws+unix:` to `ws+unix:` case.
const redirectingServerSocketPath = path.join(
os.tmpdir(),
`ws.${crypto.randomBytes(16).toString('hex' )}.sock`
);
const redirectedServerSocketPath = path.join(
os.tmpdir(),
`ws.${crypto.randomBytes(16).toString('hex' )}.sock`
);
const redirectingServer = http.createServer();
redirectingServer.on('upgrade' , (req, socket) => {
socket.end(
'HTTP/1.1 302 Found\r\n' +
`Location: ws+unix://${redirectedServerSocketPath}\r\n\r\n`
);
});
const redirectedServer = http.createServer();
const wss = new WebSocket.Server({ server: redirectedServer });
wss.on('connection' , (ws, req) => {
assert .strictEqual(req.headers.authorization, undefined);
assert .strictEqual(req.headers.cookie, undefined);
assert .strictEqual(req.headers.host, 'localhost' );
ws.close();
});
redirectingServer.listen(redirectingServerSocketPath, listening);
redirectedServer.listen(redirectedServerSocketPath, listening);
let callCount = 0;
function listening() {
if (++callCount !== 2) return ;
const headers = {
authorization: 'Basic Zm9vOmJhcg==' ,
cookie: 'foo=bar' ,
host: 'foo'
};
const ws = new WebSocket(
`ws+unix://${redirectingServerSocketPath}`,
{ followRedirects: true , headers }
);
const firstRequest = ws._req;
assert .strictEqual(
firstRequest.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(
firstRequest.getHeader('Cookie' ),
headers.cookie
);
assert .strictEqual(firstRequest.getHeader('Host' ), headers.host);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
assert .strictEqual(
ws.url,
`ws+unix://${redirectedServerSocketPath}`
);
assert .strictEqual(ws._redirects, 1);
redirectingServer.close();
redirectedServer.close(done);
});
}
});
it('drops the Authorization, Cookie and Host headers (4/4)' , function (done) {
if (process.platform === 'win32' ) return this .skip();
// Test the `ws+unix:` to `ws:` case.
const redirectingServer = http.createServer();
const redirectedServer = http.createServer();
const wss = new WebSocket.Server({ server: redirectedServer });
wss.on('connection' , (ws, req) => {
assert .strictEqual(req.headers.authorization, undefined);
assert .strictEqual(req.headers.cookie, undefined);
assert .strictEqual(
req.headers.host,
`localhost:${redirectedServer.address().port}`
);
ws.close();
});
const socketPath = path.join(
os.tmpdir(),
`ws.${crypto.randomBytes(16).toString('hex' )}.sock`
);
redirectingServer.listen(socketPath, listening);
redirectedServer.listen(0, listening);
let callCount = 0;
function listening() {
if (++callCount !== 2) return ;
const port = redirectedServer.address().port;
redirectingServer.on('upgrade' , (req, socket) => {
socket.end(
`HTTP/1.1 302 Found\r\nLocation: ws://localhost:${port}\r\n\r\n`
);
});
const headers = {
authorization: 'Basic Zm9vOmJhcg==' ,
cookie: 'foo=bar' ,
host: 'foo'
};
const ws = new WebSocket(`ws+unix://${socketPath}`, {
followRedirects: true ,
headers
});
const firstRequest = ws._req;
assert .strictEqual(
firstRequest.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(
firstRequest.getHeader('Cookie' ),
headers.cookie
);
assert .strictEqual(firstRequest.getHeader('Host' ), headers.host);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
assert .strictEqual(ws.url, `ws://localhost:${port}/`);
assert .strictEqual(ws._redirects, 1);
redirectingServer.close();
redirectedServer.close(done);
});
}
});
});
describe("If there is at least one 'redirect' event listener" , () => {
it('does not drop any headers by default' , (done) => {
const headers = {
authorization: 'Basic Zm9vOmJhcg==' ,
cookie: 'foo=bar' ,
host: 'foo'
};
const wss = new WebSocket.Server({ port: 0 }, () => {
const port = wss.address().port;
server.once('upgrade' , (req, socket) => {
socket.end(
'HTTP/1.1 302 Found\r\n' +
`Location: ws://localhost:${port}/\r\n\r\n`
);
});
const ws = new WebSocket(
`ws://localhost:${server.address().port}`,
{ followRedirects: true , headers }
);
const firstRequest = ws._req;
assert .strictEqual(
firstRequest.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(
firstRequest.getHeader('Cookie' ),
headers.cookie
);
assert .strictEqual(firstRequest.getHeader('Host' ), headers.host);
ws.on('redirect' , (url, req) => {
assert .strictEqual(ws._redirects, 1);
assert .strictEqual(url, `ws://localhost:${port}/`);
assert .notStrictEqual(firstRequest, req);
assert .strictEqual(
req.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(req.getHeader('Cookie' ), headers.cookie);
assert .strictEqual(req.getHeader('Host' ), headers.host);
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
wss.close(done);
});
});
});
wss.on('connection' , (ws, req) => {
assert .strictEqual(
req.headers.authorization,
headers.authorization
);
assert .strictEqual(req.headers.cookie, headers.cookie);
assert .strictEqual(req.headers.host, headers.host);
ws.close();
});
});
});
});
describe("In a listener of the 'redirect' event" , () => {
it('allows to abort the request without swallowing errors' , (done) => {
server.once('upgrade' , (req, socket) => {
socket.end('HTTP/1.1 302 Found\r\nLocation: /foo\r\n\r\n' );
});
const port = server.address().port;
const ws = new WebSocket(`ws://localhost:${port}`, {
followRedirects: true
});
ws.on('redirect' , (url, req) => {
assert .strictEqual(ws._redirects, 1);
assert .strictEqual(url, `ws://localhost:${port}/foo`);
req.on('socket' , () => {
req.abort();
});
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(err.message, 'socket hang up' );
ws.on('close' , (code) => {
assert .strictEqual(code, 1006);
done();
});
});
});
});
it('allows to remove headers' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const port = wss.address().port;
server.once('upgrade' , (req, socket) => {
socket.end(
'HTTP/1.1 302 Found\r\n' +
`Location: ws://localhost:${port}/\r\n\r\n`
);
});
const headers = {
authorization: 'Basic Zm9vOmJhcg==' ,
cookie: 'foo=bar'
};
const ws = new WebSocket(`ws://localhost:${server.address().port}`, {
followRedirects: true ,
headers
});
ws.on('redirect' , (url, req) => {
assert .strictEqual(ws._redirects, 1);
assert .strictEqual(url, `ws://localhost:${port}/`);
assert .strictEqual(
req.getHeader('Authorization' ),
headers.authorization
);
assert .strictEqual(req.getHeader('Cookie' ), headers.cookie);
req.removeHeader('authorization' );
req.removeHeader('cookie' );
ws.on('close' , (code) => {
assert .strictEqual(code, 1005);
wss.close(done);
});
});
});
wss.on('connection' , (ws, req) => {
assert .strictEqual(req.headers.authorization, undefined);
assert .strictEqual(req.headers.cookie, undefined);
ws.close();
});
});
});
});
describe('Connection with query string' , () => {
it('connects when pathname is not null' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const port = wss.address().port;
const ws = new WebSocket(`ws://localhost:${port}/?token=qwerty`);
ws.on('open' , () => {
wss.close(done);
});
});
wss.on('connection' , (ws) => {
ws.close();
});
});
it('connects when pathname is null' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const port = wss.address().port;
const ws = new WebSocket(`ws://localhost:${port}?token=qwerty`);
ws.on('open' , () => {
wss.close(done);
});
});
wss.on('connection' , (ws) => {
ws.close();
});
});
});
describe('#pause', () => {
it('does nothing if `readyState` is `CONNECTING` or `CLOSED`' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
assert .strictEqual(ws.readyState, WebSocket.CONNECTING);
assert .ok(!ws.isPaused);
ws.pause();
assert .ok(!ws.isPaused);
ws.on('open' , () => {
ws.on('close' , () => {
assert .strictEqual(ws.readyState, WebSocket.CLOSED);
ws.pause();
assert .ok(!ws.isPaused);
wss.close(done);
});
ws.close();
});
});
});
it('pauses the socket' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
});
wss.on('connection' , (ws) => {
assert .ok(!ws.isPaused);
assert .ok(!ws._socket.isPaused());
ws.pause();
assert .ok(ws.isPaused);
assert .ok(ws._socket.isPaused());
ws.terminate();
wss.close(done);
});
});
});
describe('#ping', () => {
it('throws an error if `readyState` is `CONNECTING`' , () => {
const ws = new WebSocket('ws://localhost' , {
lookup() {}
});
assert .throws(
() => ws.ping(),
/^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/
);
assert .throws(
() => ws.ping(NOOP),
/^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/
);
});
it('increases `bufferedAmount` if `readyState` is 2 or 3' , (done) => {
const ws = new WebSocket('ws://localhost' , {
lookup() {}
});
ws.on('error' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(
err.message,
'WebSocket was closed before the connection was established'
);
assert .strictEqual(ws.readyState, WebSocket.CLOSING);
assert .strictEqual(ws.bufferedAmount, 0);
ws.ping('hi' );
assert .strictEqual(ws.bufferedAmount, 2);
ws.ping();
assert .strictEqual(ws.bufferedAmount, 2);
ws.on('close' , () => {
assert .strictEqual(ws.readyState, WebSocket.CLOSED);
ws.ping('hi' );
assert .strictEqual(ws.bufferedAmount, 4);
ws.ping();
assert .strictEqual(ws.bufferedAmount, 4);
done();
});
});
ws.close();
});
it('calls the callback w/ an error if `readyState` is 2 or 3' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
});
wss.on('connection' , (ws) => {
ws.close();
assert .strictEqual(ws.bufferedAmount, 0);
ws.ping('hi' , (err) => {
assert .ok(err instanceof Error);
assert .strictEqual(
err.message,
'WebSocket is not open: readyState 2 (CLOSING)'
);
assert .strictEqual(ws.bufferedAmount, 2);
ws.on('close' , () => {
ws.ping((err) => {
assert .ok(err instanceof Error);
assert .strictEqual(
err.message,
'WebSocket is not open: readyState 3 (CLOSED)'
);
assert .strictEqual(ws.bufferedAmount, 2);
wss.close(done);
});
});
});
});
});
it('can send a ping with no data' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('open' , () => {
ws.ping(() => {
ws.ping();
ws.close();
});
});
});
wss.on('connection' , (ws) => {
let pings = 0;
ws.on('ping' , (data) => {
assert .ok(Buffer.isBuffer(data));
assert .strictEqual(data.length, 0);
if (++pings === 2) wss.close(done);
});
});
});
it('can send a ping with data' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('open' , () => {
ws.ping('hi' , () => {
ws.ping('hi' , true );
ws.close();
});
});
});
wss.on('connection' , (ws) => {
let pings = 0;
ws.on('ping' , (message) => {
assert .strictEqual(message.toString(), 'hi' );
if (++pings === 2) wss.close(done);
});
});
});
it('can send numbers as ping payload' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('open' , () => {
ws.ping(0);
ws.close();
});
});
wss.on('connection' , (ws) => {
ws.on('ping' , (message) => {
assert .strictEqual(message.toString(), '0' );
wss.close(done);
});
});
});
it('throws an error if the data size is greater than 125 bytes' , (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
ws.on('open' , () => {
assert .throws(
() => ws.ping(Buffer.alloc(126)),
/^RangeError: The data size must not be greater than 125 bytes$/
);
--> --------------------
--> maximum size reached
--> --------------------
quality 97%
¤ Dauer der Verarbeitung: 0.32 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland