diff --git a/lib/__tests__/integration/server.integration.test.ts b/lib/__tests__/integration/server.integration.test.ts new file mode 100644 index 0000000..e850675 --- /dev/null +++ b/lib/__tests__/integration/server.integration.test.ts @@ -0,0 +1,66 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +// import http from 'http'; +'use strict'; +import SignalingChannel from '../../server/server'; +import { WebSocket } from 'ws'; +// import { Server } from 'http'; +// import { Event, WebSocketServer } from 'ws'; + +WebSocket.EventEmitter; +beforeAll((done) => { + done(); +}); + +describe('Testing the SignalingChannel class', () => { + const sc = new SignalingChannel(65530); + const testWebsocketServer = new WebSocket.Server({ port: 5555 }); + describe('Testing SignalingChannel constructor', () => { + it('WebSocket server object is initialized', () => { + expect(typeof sc.webSocketServer).toBe('object'); + }); + + it('WebSocket server object is initialized', () => { + expect(typeof sc.webSocketServer).toBe(typeof testWebsocketServer); + }); + + it('Empty hashmap of users is initialized', () => { + expect(sc.peers.size).toBe(0); + }); + }); + + afterAll((done) => { + sc.webSocketServer.close(); + testWebsocketServer.close(); + done(); + }); +}); + +/* +describe('Checking WebSocket response', () => { + const sc1 = new SignalingChannel(3032); + + // sc1.webSocketServer.onopen; + // // Source: https://stackoverflow.com/questions/55963562/how-to-stop-jest-from-hanging-when-testing-websockets + // it('WebSocket closes connection correctly', () => { + // let disconnected = false; + // sc1.webSocketServer.addListener('close', () => { + // disconnected = true; + // sc1.webSocketServer.close(); + // }); + + // // let error: ErrorEvent; + // // ws.onerror = (e) => { + // // console.log(error) + // // error = e; + // // }; + // // expect(error.origin).toBe(`ws://localhost:${port}`); + // expect(disconnected).toBe(true); + // }); + + afterAll((done) => { + sc1.webSocketServer.close(); + done(); + }); +}); +*/ + diff --git a/lib/__tests__/unit/server.unit.test.ts b/lib/__tests__/unit/server.unit.test.ts deleted file mode 100644 index 426c245..0000000 --- a/lib/__tests__/unit/server.unit.test.ts +++ /dev/null @@ -1,4322 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -// import http from 'http'; -'use strict'; -import SignalingChannel from '../../server/server'; -import { WebSocket } from 'ws'; -// import { Server } from 'http'; -// import { Event, WebSocketServer } from 'ws'; - -WebSocket.EventEmitter; -beforeAll((done) => { - done(); -}); - -describe('Testing the SignalingChannel class', () => { - const sc = new SignalingChannel(65530); - const testWebsocketServer = new WebSocket.Server({ port: 5555 }); - describe('Testing SignalingChannel constructor', () => { - it('WebSocket server object is initialized', () => { - expect(typeof sc.webSocketServer).toBe('object'); - }); - - it('WebSocket server object is initialized', () => { - expect(typeof sc.webSocketServer).toBe(typeof testWebsocketServer); - }); - - it('Empty hashmap of users is initialized', () => { - expect(sc.peers.size).toBe(0); - }); - }); - - afterAll((done) => { - sc.webSocketServer.close(); - testWebsocketServer.close(); - done(); - }); -}); - -/* -describe('Checking WebSocket response', () => { - const sc1 = new SignalingChannel(3032); - - // sc1.webSocketServer.onopen; - // // Source: https://stackoverflow.com/questions/55963562/how-to-stop-jest-from-hanging-when-testing-websockets - // it('WebSocket closes connection correctly', () => { - // let disconnected = false; - // sc1.webSocketServer.addListener('close', () => { - // disconnected = true; - // sc1.webSocketServer.close(); - // }); - - // // let error: ErrorEvent; - // // ws.onerror = (e) => { - // // console.log(error) - // // error = e; - // // }; - // // expect(error.origin).toBe(`ws://localhost:${port}`); - // expect(disconnected).toBe(true); - // }); - - afterAll((done) => { - sc1.webSocketServer.close(); - done(); - }); -}); -*/ - - - -// const assert = require('assert'); -// const crypto = require('crypto'); -// const https = require('https'); -// const path = require('path'); -// const net = require('net'); -// const tls = require('tls'); -// const os = require('os'); -// const fs = require('fs'); -// const { getDefaultHighWaterMark } = require('stream'); -// 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'); - -// const highWaterMark = getDefaultHighWaterMark -// ? getDefaultHighWaterMark(false) -// : 16 * 1024; - -// class CustomAgent extends http.Agent { -// addRequest() {} -// } - -describe('WebSocket checks', () => { - describe('WebSocket invalid url checks', () => { - it('throws an error when using an invalid url', () => { - - expect(() => new WebSocket('typo')).toThrow(SyntaxError); - - expect(() => new WebSocket('invalid://websocket-echo.com')).toThrow(new Error('The URL\'s protocol must be one of "ws:", "wss:", or "ws+unix:"')); - - // URL's pathname is empty - expect(() => new WebSocket('ws+unix:')).toThrow(SyntaxError); - - expect(() => new WebSocket('wss://websocket-echo.com#foo')).toThrow(/The URL contains a fragment identifier/); - }); - - afterAll((done) => { - done(); - }); - - // it('allows the http scheme', (done) => { - // const agent = new CustomAgent(); - - // agent.addRequest = (req, opts) => { - // expect(() => opts.port.toBe(80)); - // }; - - // const ws = new WebSocket('http://localhost', { agent }); - // expect(() => ws.host.toMatch('localhost')); - - // }); - - // it('allows the https scheme', (done) => { - // const agent = new https.Agent(); - - // agent.addRequest = (req, opts) => { - // assert.strictEqual(opts.host, 'localhost'); - // assert.strictEqual(opts.port, 443); - // done(); - // }; - - // const ws = new WebSocket('https://localhost', { agent }); - // }); - }); - - - // Determine with team which checks are needed - /* - 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('`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 schemes = new Map([ - ['ws', 'ws'], - ['wss', 'wss'], - ['http', 'ws'], - ['https', 'wss'] - ]); - - for (const [key, value] of schemes) { - const ws = new WebSocket(`${key}://localhost/`, { lookup() {} }); - - assert.strictEqual(ws.url, `${value}://localhost/`); - } - }); - }); - }); - - 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', function (done) { - // - // `socket.resetAndDestroy()` is not available in Node.js < 16.17.0. - // - if (process.versions.modules < 93) return this.skip(); - - 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.strictEqual(err.code, 'ECONNRESET'); - ws.on('close', (code, message) => { - assert.strictEqual(code, 1006); - assert.strictEqual(message, EMPTY_BUFFER); - wss.close(done); - }); - }); - - wss.clients.values().next().value._socket.resetAndDestroy(); - }); - }); - }); - - 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); - }); - }); - }); - }); - }); - */ - - // Next set of tests to implement - /* - 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: bad-scheme://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:", ' + - '"http:", "https", 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)', (done) => { - // Test the `ws:` to `ws+unix:` case. - - const randomString = crypto.randomBytes(16).toString('hex'); - const ipcPath = - process.platform === 'win32' - ? `\\\\.\\pipe\\ws-pipe-${randomString}` - : path.join(os.tmpdir(), `ws-${randomString}.sock`); - - server.once('upgrade', (req, socket) => { - socket.end( - `HTTP/1.1 302 Found\r\nLocation: ws+unix:${ipcPath}\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(ipcPath, () => { - 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:${ipcPath}`); - assert.strictEqual(ws._redirects, 1); - - redirectedServer.close(done); - }); - }); - }); - - it('drops the Authorization, Cookie and Host headers (3/4)', (done) => { - // Test the `ws+unix:` to `ws+unix:` case. - - const randomString1 = crypto.randomBytes(16).toString('hex'); - const randomString2 = crypto.randomBytes(16).toString('hex'); - let redirectingServerIpcPath; - let redirectedServerIpcPath; - - if (process.platform === 'win32') { - redirectingServerIpcPath = `\\\\.\\pipe\\ws-pipe-${randomString1}`; - redirectedServerIpcPath = `\\\\.\\pipe\\ws-pipe-${randomString2}`; - } else { - redirectingServerIpcPath = path.join( - os.tmpdir(), - `ws-${randomString1}.sock` - ); - redirectedServerIpcPath = path.join( - os.tmpdir(), - `ws-${randomString2}.sock` - ); - } - - const redirectingServer = http.createServer(); - - redirectingServer.on('upgrade', (req, socket) => { - socket.end( - 'HTTP/1.1 302 Found\r\n' + - `Location: ws+unix:${redirectedServerIpcPath}\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(redirectingServerIpcPath, listening); - redirectedServer.listen(redirectedServerIpcPath, 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:${redirectingServerIpcPath}`, { - 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:${redirectedServerIpcPath}`); - assert.strictEqual(ws._redirects, 1); - - redirectingServer.close(); - redirectedServer.close(done); - }); - } - }); - - it('drops the Authorization, Cookie and Host headers (4/4)', (done) => { - // 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 randomString = crypto.randomBytes(16).toString('hex'); - const ipcPath = - process.platform === 'win32' - ? `\\\\.\\pipe\\ws-pipe-${randomString}` - : path.join(os.tmpdir(), `ws-${randomString}.sock`); - - redirectingServer.listen(ipcPath, 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:${ipcPath}`, { - 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('#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$/ - ); - - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.close(); - }); - }); - }); - - describe('#pong', () => { - it('throws an error if `readyState` is `CONNECTING`', () => { - const ws = new WebSocket('ws://localhost', { - lookup() {} - }); - - assert.throws( - () => ws.pong(), - /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ - ); - - assert.throws( - () => ws.pong(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.pong('hi'); - assert.strictEqual(ws.bufferedAmount, 2); - - ws.pong(); - assert.strictEqual(ws.bufferedAmount, 2); - - ws.on('close', () => { - assert.strictEqual(ws.readyState, WebSocket.CLOSED); - - ws.pong('hi'); - assert.strictEqual(ws.bufferedAmount, 4); - - ws.pong(); - 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.pong('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.pong((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 pong with no data', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.pong(() => { - ws.pong(); - ws.close(); - }); - }); - }); - - wss.on('connection', (ws) => { - let pongs = 0; - ws.on('pong', (data) => { - assert.ok(Buffer.isBuffer(data)); - assert.strictEqual(data.length, 0); - if (++pongs === 2) wss.close(done); - }); - }); - }); - - it('can send a pong with data', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.pong('hi', () => { - ws.pong('hi', true); - ws.close(); - }); - }); - }); - - wss.on('connection', (ws) => { - let pongs = 0; - ws.on('pong', (message) => { - assert.strictEqual(message.toString(), 'hi'); - if (++pongs === 2) wss.close(done); - }); - }); - }); - - it('can send numbers as pong payload', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.pong(0); - ws.close(); - }); - }); - - wss.on('connection', (ws) => { - ws.on('pong', (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.pong(Buffer.alloc(126)), - /^RangeError: The data size must not be greater than 125 bytes$/ - ); - - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.close(); - }); - }); - - it('is called automatically when a ping is received', (done) => { - const buf = Buffer.from('hi'); - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.ping(buf); - }); - - ws.on('pong', (data) => { - assert.deepStrictEqual(data, buf); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.on('ping', (data) => { - assert.deepStrictEqual(data, buf); - ws.close(); - }); - }); - }); - }); - - describe('#resume', () => { - 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); - - // Verify that no exception is thrown. - ws.resume(); - - ws.on('open', () => { - ws.pause(); - assert.ok(ws.isPaused); - - ws.on('close', () => { - assert.strictEqual(ws.readyState, WebSocket.CLOSED); - - ws.resume(); - assert.ok(ws.isPaused); - - wss.close(done); - }); - - ws.terminate(); - }); - }); - }); - - it('resumes 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.resume(); - assert.ok(!ws.isPaused); - assert.ok(!ws._socket.isPaused()); - - ws.close(); - wss.close(done); - }); - }); - }); - - describe('#send', () => { - it('throws an error if `readyState` is `CONNECTING`', () => { - const ws = new WebSocket('ws://localhost', { - lookup() {} - }); - - assert.throws( - () => ws.send('hi'), - /^Error: WebSocket is not open: readyState 0 \(CONNECTING\)$/ - ); - - assert.throws( - () => ws.send('hi', 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.send('hi'); - assert.strictEqual(ws.bufferedAmount, 2); - - ws.send(); - assert.strictEqual(ws.bufferedAmount, 2); - - ws.on('close', () => { - assert.strictEqual(ws.readyState, WebSocket.CLOSED); - - ws.send('hi'); - assert.strictEqual(ws.bufferedAmount, 4); - - ws.send(); - 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.send('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.send('hi', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket is not open: readyState 3 (CLOSED)' - ); - assert.strictEqual(ws.bufferedAmount, 4); - - wss.close(done); - }); - }); - }); - }); - }); - - it('can send a big binary message', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const array = new Float32Array(1024 * 1024); - - for (let i = 0; i < array.length; i++) { - array[i] = i / 5; - } - - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => ws.send(array)); - ws.on('message', (msg, isBinary) => { - assert.deepStrictEqual(msg, Buffer.from(array.buffer)); - assert.ok(isBinary); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (msg, isBinary) => { - assert.ok(isBinary); - ws.send(msg); - ws.close(); - }); - }); - }); - - it('can send text data', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => ws.send('hi')); - ws.on('message', (message, isBinary) => { - assert.deepStrictEqual(message, Buffer.from('hi')); - assert.ok(!isBinary); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (msg, isBinary) => { - ws.send(msg, { binary: isBinary }); - ws.close(); - }); - }); - }); - - it('sends numbers as strings', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.send(0); - ws.close(); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (msg, isBinary) => { - assert.deepStrictEqual(msg, Buffer.from('0')); - assert.ok(!isBinary); - wss.close(done); - }); - }); - }); - - it('can send a `TypedArray`', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const array = new Float32Array(6); - - for (let i = 0; i < array.length; ++i) { - array[i] = i / 2; - } - - const partial = array.subarray(2, 5); - const buf = Buffer.from( - partial.buffer, - partial.byteOffset, - partial.byteLength - ); - - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.send(partial); - ws.close(); - }); - - ws.on('message', (message, isBinary) => { - assert.deepStrictEqual(message, buf); - assert.ok(isBinary); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (msg, isBinary) => { - assert.ok(isBinary); - ws.send(msg); - }); - }); - }); - - it('can send an `ArrayBuffer`', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const array = new Float32Array(5); - - for (let i = 0; i < array.length; ++i) { - array[i] = i / 2; - } - - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.send(array.buffer); - ws.close(); - }); - - ws.onmessage = (event) => { - assert.ok(event.data.equals(Buffer.from(array.buffer))); - wss.close(done); - }; - }); - - wss.on('connection', (ws) => { - ws.on('message', (msg, isBinary) => { - assert.ok(isBinary); - ws.send(msg); - }); - }); - }); - - it('can send a `Buffer`', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const buf = Buffer.from('foobar'); - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.send(buf); - ws.close(); - }); - - ws.onmessage = (event) => { - assert.deepStrictEqual(event.data, buf); - wss.close(done); - }; - }); - - wss.on('connection', (ws) => { - ws.on('message', (msg, isBinary) => { - assert.ok(isBinary); - ws.send(msg); - }); - }); - }); - - it('calls the callback when data is written out', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.send('hi', (err) => { - assert.ifError(err); - wss.close(done); - }); - }); - }); - - wss.on('connection', (ws) => { - ws.close(); - }); - }); - - it('works when the `data` argument is falsy', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws.send(); - ws.close(); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (message, isBinary) => { - assert.strictEqual(message, EMPTY_BUFFER); - assert.ok(isBinary); - wss.close(done); - }); - }); - }); - }); - */ - - - - // ASK Anthony to Review - /* - describe('#close', () => { - it('closes the connection if called while connecting (1/3)', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => done(new Error("Unexpected 'open' event"))); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - ws.on('close', () => wss.close(done)); - }); - ws.close(1001); - }); - }); - - it('closes the connection if called while connecting (2/3)', (done) => { - const wss = new WebSocket.Server( - { - verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: 0 - }, - () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => done(new Error("Unexpected 'open' event"))); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - ws.on('close', () => wss.close(done)); - }); - setTimeout(() => ws.close(1001), 150); - } - ); - }); - - it('closes the connection if called while connecting (3/3)', (done) => { - const server = http.createServer(); - - server.listen(0, () => { - 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, - 'WebSocket was closed before the connection was established' - ); - ws.on('close', () => { - server.close(done); - }); - }); - - ws.on('unexpected-response', (req, res) => { - assert.strictEqual(res.statusCode, 502); - - const chunks = []; - - res.on('data', (chunk) => { - chunks.push(chunk); - }); - - res.on('end', () => { - assert.strictEqual(Buffer.concat(chunks).toString(), 'foo'); - ws.close(); - }); - }); - }); - - server.on('upgrade', (req, socket) => { - socket.on('end', socket.end); - - socket.write( - `HTTP/1.1 502 ${http.STATUS_CODES[502]}\r\n` + - 'Connection: keep-alive\r\n' + - 'Content-type: text/html\r\n' + - 'Content-Length: 3\r\n' + - '\r\n' + - 'foo' - ); - }); - }); - - it('can be called from an error listener while connecting', (done) => { - const server = net.createServer(); - - server.on('connection', (socket) => { - socket.on('end', socket.end); - socket.resume(); - socket.write(Buffer.from('foo\r\n')); - }); - - server.listen(0, () => { - 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.code, 'HPE_INVALID_CONSTANT'); - ws.close(); - ws.on('close', () => { - server.close(done); - }); - }); - }); - }); - - it("can be called from a listener of the 'redirect' event", (done) => { - const server = http.createServer(); - - server.once('upgrade', (req, socket) => { - socket.end('HTTP/1.1 302 Found\r\nLocation: /foo\r\n\r\n'); - }); - - server.listen(() => { - const port = server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - followRedirects: true - }); - - ws.on('open', () => { - done(new Error("Unexpected 'open' event")); - }); - - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - - ws.on('close', (code) => { - assert.strictEqual(code, 1006); - server.close(done); - }); - }); - - ws.on('redirect', () => { - ws.close(); - }); - }); - }); - - it("can be called from a listener of the 'upgrade' event", (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => done(new Error("Unexpected 'open' event"))); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - ws.on('close', () => wss.close(done)); - }); - ws.on('upgrade', () => ws.close()); - }); - }); - - it('sends the close status code only when necessary', (done) => { - let sent; - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws._socket.once('data', (data) => { - sent = data; - }); - }); - }); - - wss.on('connection', (ws) => { - ws._socket.once('data', (received) => { - assert.deepStrictEqual( - received.slice(0, 2), - Buffer.from([0x88, 0x80]) - ); - assert.deepStrictEqual(sent, Buffer.from([0x88, 0x00])); - - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1005); - assert.strictEqual(reason, EMPTY_BUFFER); - wss.close(done); - }); - }); - ws.close(); - }); - }); - - it('works when close reason is not specified', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => ws.close(1000)); - }); - - wss.on('connection', (ws) => { - ws.on('close', (code, message) => { - assert.strictEqual(code, 1000); - assert.deepStrictEqual(message, EMPTY_BUFFER); - wss.close(done); - }); - }); - }); - - it('works when close reason is specified', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => ws.close(1000, 'some reason')); - }); - - wss.on('connection', (ws) => { - ws.on('close', (code, message) => { - assert.strictEqual(code, 1000); - assert.deepStrictEqual(message, Buffer.from('some reason')); - wss.close(done); - }); - }); - }); - - it('permits all buffered data to be delivered', (done) => { - const wss = new WebSocket.Server( - { - perMessageDeflate: { threshold: 0 }, - port: 0 - }, - () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - const messages = []; - - ws.on('message', (message, isBinary) => { - assert.ok(!isBinary); - messages.push(message.toString()); - }); - ws.on('close', (code) => { - assert.strictEqual(code, 1005); - assert.deepStrictEqual(messages, ['foo', 'bar', 'baz']); - wss.close(done); - }); - } - ); - - wss.on('connection', (ws) => { - const callback = (err) => assert.ifError(err); - - ws.send('foo', callback); - ws.send('bar', callback); - ws.send('baz', callback); - ws.close(); - ws.close(); - }); - }); - - it('allows close code 1013', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('close', (code) => { - assert.strictEqual(code, 1013); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => ws.close(1013)); - }); - - it('allows close code 1014', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('close', (code) => { - assert.strictEqual(code, 1014); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => ws.close(1014)); - }); - - it('does nothing if `readyState` is `CLOSED`', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('close', (code) => { - assert.strictEqual(code, 1005); - assert.strictEqual(ws.readyState, WebSocket.CLOSED); - ws.close(); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => ws.close()); - }); - - it('sets a timer for the closing handshake to complete', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1000); - assert.deepStrictEqual(reason, Buffer.from('some reason')); - wss.close(done); - }); - - ws.on('open', () => { - let callbackCalled = false; - - assert.strictEqual(ws._closeTimer, null); - - ws.send('foo', () => { - callbackCalled = true; - }); - - ws.close(1000, 'some reason'); - - // - // Check that the close timer is set even if the `Sender.close()` - // callback is not called. - // - assert.strictEqual(callbackCalled, false); - assert.strictEqual(ws._closeTimer._idleTimeout, 30000); - }); - }); - }); - }); - */ - - - - // Determine with team which tests - /* - describe('#terminate', () => { - it('closes the connection if called while connecting (1/2)', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => done(new Error("Unexpected 'open' event"))); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - ws.on('close', () => wss.close(done)); - }); - ws.terminate(); - }); - }); - - it('closes the connection if called while connecting (2/2)', (done) => { - const wss = new WebSocket.Server( - { - verifyClient: (info, cb) => setTimeout(cb, 300, true), - port: 0 - }, - () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => done(new Error("Unexpected 'open' event"))); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - ws.on('close', () => wss.close(done)); - }); - setTimeout(() => ws.terminate(), 150); - } - ); - }); - - it('can be called from an error listener while connecting', (done) => { - const server = net.createServer(); - - server.on('connection', (socket) => { - socket.on('end', socket.end); - socket.resume(); - socket.write(Buffer.from('foo\r\n')); - }); - - server.listen(0, () => { - 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.code, 'HPE_INVALID_CONSTANT'); - ws.terminate(); - ws.on('close', () => { - server.close(done); - }); - }); - }); - }); - - it("can be called from a listener of the 'redirect' event", (done) => { - const server = http.createServer(); - - server.once('upgrade', (req, socket) => { - socket.end('HTTP/1.1 302 Found\r\nLocation: /foo\r\n\r\n'); - }); - - server.listen(() => { - const port = server.address().port; - const ws = new WebSocket(`ws://localhost:${port}`, { - followRedirects: true - }); - - ws.on('open', () => { - done(new Error("Unexpected 'open' event")); - }); - - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - - ws.on('close', (code) => { - assert.strictEqual(code, 1006); - server.close(done); - }); - }); - - ws.on('redirect', () => { - ws.terminate(); - }); - }); - }); - - it("can be called from a listener of the 'upgrade' event", (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => done(new Error("Unexpected 'open' event"))); - ws.on('error', (err) => { - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'WebSocket was closed before the connection was established' - ); - ws.on('close', () => wss.close(done)); - }); - ws.on('upgrade', () => ws.terminate()); - }); - }); - - it('does nothing if `readyState` is `CLOSED`', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('close', (code) => { - assert.strictEqual(code, 1006); - assert.strictEqual(ws.readyState, WebSocket.CLOSED); - ws.terminate(); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => ws.terminate()); - }); - }); - - describe('WHATWG API emulation', () => { - it('supports the `on{close,error,message,open}` attributes', () => { - for (const property of ['onclose', 'onerror', 'onmessage', 'onopen']) { - const descriptor = Object.getOwnPropertyDescriptor( - WebSocket.prototype, - property - ); - - assert.strictEqual(descriptor.configurable, true); - assert.strictEqual(descriptor.enumerable, true); - assert.ok(descriptor.get !== undefined); - assert.ok(descriptor.set !== undefined); - } - - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - assert.strictEqual(ws.onmessage, null); - assert.strictEqual(ws.onclose, null); - assert.strictEqual(ws.onerror, null); - assert.strictEqual(ws.onopen, null); - - ws.onmessage = NOOP; - ws.onerror = NOOP; - ws.onclose = NOOP; - ws.onopen = NOOP; - - assert.strictEqual(ws.onmessage, NOOP); - assert.strictEqual(ws.onclose, NOOP); - assert.strictEqual(ws.onerror, NOOP); - assert.strictEqual(ws.onopen, NOOP); - - ws.onmessage = 'foo'; - - assert.strictEqual(ws.onmessage, null); - assert.strictEqual(ws.listenerCount('message'), 0); - }); - - it('works like the `EventEmitter` interface', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.onmessage = (messageEvent) => { - assert.strictEqual(messageEvent.data, 'foo'); - ws.onclose = (closeEvent) => { - assert.strictEqual(closeEvent.wasClean, true); - assert.strictEqual(closeEvent.code, 1005); - assert.strictEqual(closeEvent.reason, ''); - wss.close(done); - }; - ws.close(); - }; - - ws.onopen = () => ws.send('foo'); - }); - - wss.on('connection', (ws) => { - ws.on('message', (msg, isBinary) => { - ws.send(msg, { binary: isBinary }); - }); - }); - }); - - it("doesn't return listeners added with `on`", () => { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - ws.on('open', NOOP); - - assert.deepStrictEqual(ws.listeners('open'), [NOOP]); - assert.strictEqual(ws.onopen, null); - }); - - it("doesn't remove listeners added with `on`", () => { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - ws.on('close', NOOP); - ws.onclose = NOOP; - - let listeners = ws.listeners('close'); - - assert.strictEqual(listeners.length, 2); - assert.strictEqual(listeners[0], NOOP); - assert.strictEqual(listeners[1][kListener], NOOP); - - ws.onclose = NOOP; - - listeners = ws.listeners('close'); - - assert.strictEqual(listeners.length, 2); - assert.strictEqual(listeners[0], NOOP); - assert.strictEqual(listeners[1][kListener], NOOP); - }); - - it('supports the `addEventListener` method', () => { - const events = []; - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - ws.addEventListener('foo', () => {}); - assert.strictEqual(ws.listenerCount('foo'), 0); - - function onOpen() { - events.push('open'); - assert.strictEqual(ws.listenerCount('open'), 1); - } - - ws.addEventListener('open', onOpen); - ws.addEventListener('open', onOpen); - - assert.strictEqual(ws.listenerCount('open'), 1); - - const listener = { - handleEvent() { - events.push('message'); - assert.strictEqual(this, listener); - assert.strictEqual(ws.listenerCount('message'), 0); - } - }; - - ws.addEventListener('message', listener, { once: true }); - ws.addEventListener('message', listener); - - assert.strictEqual(ws.listenerCount('message'), 1); - - ws.addEventListener('close', NOOP); - ws.onclose = NOOP; - - let listeners = ws.listeners('close'); - - assert.strictEqual(listeners.length, 2); - assert.strictEqual(listeners[0][kListener], NOOP); - assert.strictEqual(listeners[1][kListener], NOOP); - - ws.onerror = NOOP; - ws.addEventListener('error', NOOP); - - listeners = ws.listeners('error'); - - assert.strictEqual(listeners.length, 2); - assert.strictEqual(listeners[0][kListener], NOOP); - assert.strictEqual(listeners[1][kListener], NOOP); - - ws.emit('open'); - ws.emit('message', EMPTY_BUFFER, false); - - assert.deepStrictEqual(events, ['open', 'message']); - }); - - it("doesn't return listeners added with `addEventListener`", () => { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - ws.addEventListener('open', NOOP); - - const listeners = ws.listeners('open'); - - assert.strictEqual(listeners.length, 1); - assert.strictEqual(listeners[0][kListener], NOOP); - - assert.strictEqual(ws.onopen, null); - }); - - it("doesn't remove listeners added with `addEventListener`", () => { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - ws.addEventListener('close', NOOP); - ws.onclose = NOOP; - - let listeners = ws.listeners('close'); - - assert.strictEqual(listeners.length, 2); - assert.strictEqual(listeners[0][kListener], NOOP); - assert.strictEqual(listeners[1][kListener], NOOP); - - ws.onclose = NOOP; - - listeners = ws.listeners('close'); - - assert.strictEqual(listeners.length, 2); - assert.strictEqual(listeners[0][kListener], NOOP); - assert.strictEqual(listeners[1][kListener], NOOP); - }); - - it('supports the `removeEventListener` method', () => { - const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - - const listener = { handleEvent() {} }; - - ws.addEventListener('message', listener); - ws.addEventListener('open', NOOP); - - assert.strictEqual(ws.listeners('message')[0][kListener], listener); - assert.strictEqual(ws.listeners('open')[0][kListener], NOOP); - - ws.removeEventListener('message', () => {}); - - assert.strictEqual(ws.listeners('message')[0][kListener], listener); - - ws.removeEventListener('message', listener); - ws.removeEventListener('open', NOOP); - - assert.strictEqual(ws.listenerCount('message'), 0); - assert.strictEqual(ws.listenerCount('open'), 0); - - ws.addEventListener('message', NOOP, { once: true }); - ws.addEventListener('open', NOOP, { once: true }); - - assert.strictEqual(ws.listeners('message')[0][kListener], NOOP); - assert.strictEqual(ws.listeners('open')[0][kListener], NOOP); - - ws.removeEventListener('message', () => {}); - - assert.strictEqual(ws.listeners('message')[0][kListener], NOOP); - - ws.removeEventListener('message', NOOP); - ws.removeEventListener('open', NOOP); - - assert.strictEqual(ws.listenerCount('message'), 0); - assert.strictEqual(ws.listenerCount('open'), 0); - - // Listeners not added with `websocket.addEventListener()`. - ws.on('message', NOOP); - - assert.deepStrictEqual(ws.listeners('message'), [NOOP]); - - ws.removeEventListener('message', NOOP); - - assert.deepStrictEqual(ws.listeners('message'), [NOOP]); - - ws.onclose = NOOP; - - assert.strictEqual(ws.listeners('close')[0][kListener], NOOP); - - ws.removeEventListener('close', NOOP); - - assert.strictEqual(ws.listeners('close')[0][kListener], NOOP); - }); - - it('wraps text data in a `MessageEvent`', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.addEventListener('open', () => { - ws.send('hi'); - ws.close(); - }); - - ws.addEventListener('message', (event) => { - assert.ok(event instanceof MessageEvent); - assert.strictEqual(event.data, 'hi'); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (msg, isBinary) => { - ws.send(msg, { binary: isBinary }); - }); - }); - }); - - it('receives a `CloseEvent` when server closes (1000)', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.addEventListener('close', (event) => { - assert.ok(event instanceof CloseEvent); - assert.ok(event.wasClean); - assert.strictEqual(event.reason, ''); - assert.strictEqual(event.code, 1000); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => ws.close(1000)); - }); - - it('receives a `CloseEvent` when server closes (4000)', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.addEventListener('close', (event) => { - assert.ok(event instanceof CloseEvent); - assert.ok(event.wasClean); - assert.strictEqual(event.reason, 'some daft reason'); - assert.strictEqual(event.code, 4000); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => ws.close(4000, 'some daft reason')); - }); - - it('sets `target` and `type` on events', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const err = new Error('forced'); - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.addEventListener('open', (event) => { - assert.ok(event instanceof Event); - assert.strictEqual(event.type, 'open'); - assert.strictEqual(event.target, ws); - }); - ws.addEventListener('message', (event) => { - assert.ok(event instanceof MessageEvent); - assert.strictEqual(event.type, 'message'); - assert.strictEqual(event.target, ws); - ws.close(); - }); - ws.addEventListener('close', (event) => { - assert.ok(event instanceof CloseEvent); - assert.strictEqual(event.type, 'close'); - assert.strictEqual(event.target, ws); - ws.emit('error', err); - }); - ws.addEventListener('error', (event) => { - assert.ok(event instanceof ErrorEvent); - assert.strictEqual(event.message, 'forced'); - assert.strictEqual(event.type, 'error'); - assert.strictEqual(event.target, ws); - assert.strictEqual(event.error, err); - - wss.close(done); - }); - }); - - wss.on('connection', (client) => client.send('hi')); - }); - - it('passes binary data as a Node.js `Buffer` by default', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.onmessage = (evt) => { - assert.ok(Buffer.isBuffer(evt.data)); - wss.close(done); - }; - }); - - wss.on('connection', (ws) => { - ws.send(new Uint8Array(4096)); - ws.close(); - }); - }); - - it('ignores `binaryType` for text messages', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.binaryType = 'arraybuffer'; - - ws.onmessage = (evt) => { - assert.strictEqual(evt.data, 'foo'); - wss.close(done); - }; - }); - - wss.on('connection', (ws) => { - ws.send('foo'); - ws.close(); - }); - }); - - it('allows to update `binaryType` on the fly', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - function testType(binaryType, next) { - const buf = Buffer.from(binaryType); - ws.binaryType = binaryType; - - ws.onmessage = (evt) => { - if (binaryType === 'nodebuffer') { - assert.ok(Buffer.isBuffer(evt.data)); - assert.ok(evt.data.equals(buf)); - } else if (binaryType === 'arraybuffer') { - assert.ok(evt.data instanceof ArrayBuffer); - assert.ok(Buffer.from(evt.data).equals(buf)); - } else if (binaryType === 'fragments') { - assert.deepStrictEqual(evt.data, [buf]); - } - next(); - }; - - ws.send(buf); - } - - ws.onopen = () => { - testType('nodebuffer', () => { - testType('arraybuffer', () => { - testType('fragments', () => { - ws.close(); - wss.close(done); - }); - }); - }); - }; - }); - - wss.on('connection', (ws) => { - ws.on('message', (msg, isBinary) => { - assert.ok(isBinary); - ws.send(msg); - }); - }); - }); - }); - - describe('SSL', () => { - it('connects to secure websocket server', (done) => { - const server = https.createServer({ - cert: fs.readFileSync('test/fixtures/certificate.pem'), - key: fs.readFileSync('test/fixtures/key.pem') - }); - const wss = new WebSocket.Server({ server }); - - wss.on('connection', () => { - server.close(done); - }); - - server.listen(0, () => { - const ws = new WebSocket(`wss://127.0.0.1:${server.address().port}`, { - rejectUnauthorized: false - }); - - ws.on('open', ws.close); - }); - }); - - it('connects to secure websocket server with client side certificate', (done) => { - const server = https.createServer({ - cert: fs.readFileSync('test/fixtures/certificate.pem'), - ca: [fs.readFileSync('test/fixtures/ca-certificate.pem')], - key: fs.readFileSync('test/fixtures/key.pem'), - requestCert: true - }); - - const wss = new WebSocket.Server({ noServer: true }); - - server.on('upgrade', (request, socket, head) => { - assert.ok(socket.authorized); - - wss.handleUpgrade(request, socket, head, (ws) => { - ws.on('close', (code) => { - assert.strictEqual(code, 1005); - server.close(done); - }); - }); - }); - - server.listen(0, () => { - const ws = new WebSocket(`wss://localhost:${server.address().port}`, { - cert: fs.readFileSync('test/fixtures/client-certificate.pem'), - key: fs.readFileSync('test/fixtures/client-key.pem'), - rejectUnauthorized: false - }); - - ws.on('open', ws.close); - }); - }); - - it('cannot connect to secure websocket server via ws://', (done) => { - const server = https.createServer({ - cert: fs.readFileSync('test/fixtures/certificate.pem'), - key: fs.readFileSync('test/fixtures/key.pem') - }); - const wss = new WebSocket.Server({ server }); - - server.listen(0, () => { - const ws = new WebSocket(`ws://localhost:${server.address().port}`, { - rejectUnauthorized: false - }); - - ws.on('error', () => { - server.close(done); - wss.close(); - }); - }); - }); - - it('can send and receive text data', (done) => { - const server = https.createServer({ - cert: fs.readFileSync('test/fixtures/certificate.pem'), - key: fs.readFileSync('test/fixtures/key.pem') - }); - const wss = new WebSocket.Server({ server }); - - wss.on('connection', (ws) => { - ws.on('message', (message, isBinary) => { - assert.deepStrictEqual(message, Buffer.from('foobar')); - assert.ok(!isBinary); - server.close(done); - }); - }); - - server.listen(0, () => { - const ws = new WebSocket(`wss://localhost:${server.address().port}`, { - rejectUnauthorized: false - }); - - ws.on('open', () => { - ws.send('foobar'); - ws.close(); - }); - }); - }); - - it('can send a big binary message', (done) => { - const buf = crypto.randomBytes(5 * 1024 * 1024); - const server = https.createServer({ - cert: fs.readFileSync('test/fixtures/certificate.pem'), - key: fs.readFileSync('test/fixtures/key.pem') - }); - const wss = new WebSocket.Server({ server }); - - wss.on('connection', (ws) => { - ws.on('message', (message, isBinary) => { - assert.ok(isBinary); - ws.send(message); - ws.close(); - }); - }); - - server.listen(0, () => { - const ws = new WebSocket(`wss://localhost:${server.address().port}`, { - rejectUnauthorized: false - }); - - ws.on('open', () => ws.send(buf)); - ws.on('message', (message, isBinary) => { - assert.deepStrictEqual(message, buf); - assert.ok(isBinary); - - server.close(done); - }); - }); - }).timeout(4000); - - it('allows to disable sending the SNI extension', (done) => { - const original = tls.connect; - - tls.connect = (options) => { - assert.strictEqual(options.servername, ''); - tls.connect = original; - done(); - }; - - const ws = new WebSocket('wss://127.0.0.1', { servername: '' }); - }); - - it("works around a double 'error' event bug in Node.js", function (done) { - // - // The `minVersion` and `maxVersion` options are not supported in - // Node.js < 10.16.0. - // - if (process.versions.modules < 64) return this.skip(); - - // - // The `'error'` event can be emitted multiple times by the - // `http.ClientRequest` object in Node.js < 13. This test reproduces the - // issue in Node.js 12. - // - const server = https.createServer({ - cert: fs.readFileSync('test/fixtures/certificate.pem'), - key: fs.readFileSync('test/fixtures/key.pem'), - minVersion: 'TLSv1.2' - }); - const wss = new WebSocket.Server({ server }); - - server.listen(0, () => { - const ws = new WebSocket(`wss://localhost:${server.address().port}`, { - maxVersion: 'TLSv1.1', - rejectUnauthorized: false - }); - - ws.on('error', (err) => { - assert.ok(err instanceof Error); - server.close(done); - wss.close(); - }); - }); - }); - }); - - describe('Request headers', () => { - it('adds the authorization header if the url has userinfo', (done) => { - const agent = new http.Agent(); - const userinfo = 'test:testpass'; - - agent.addRequest = (req) => { - assert.strictEqual( - req.getHeader('authorization'), - `Basic ${Buffer.from(userinfo).toString('base64')}` - ); - done(); - }; - - const ws = new WebSocket(`ws://${userinfo}@localhost`, { agent }); - }); - - it('honors the `auth` option', (done) => { - const agent = new http.Agent(); - const auth = 'user:pass'; - - agent.addRequest = (req) => { - assert.strictEqual( - req.getHeader('authorization'), - `Basic ${Buffer.from(auth).toString('base64')}` - ); - done(); - }; - - const ws = new WebSocket('ws://localhost', { agent, auth }); - }); - - it('favors the url userinfo over the `auth` option', (done) => { - const agent = new http.Agent(); - const auth = 'foo:bar'; - const userinfo = 'baz:qux'; - - agent.addRequest = (req) => { - assert.strictEqual( - req.getHeader('authorization'), - `Basic ${Buffer.from(userinfo).toString('base64')}` - ); - done(); - }; - - const ws = new WebSocket(`ws://${userinfo}@localhost`, { agent, auth }); - }); - - it('adds custom headers', (done) => { - const agent = new http.Agent(); - - agent.addRequest = (req) => { - assert.strictEqual(req.getHeader('cookie'), 'foo=bar'); - done(); - }; - - const ws = new WebSocket('ws://localhost', { - headers: { Cookie: 'foo=bar' }, - agent - }); - }); - - it('excludes default ports from host header', () => { - const options = { lookup() {} }; - const variants = [ - ['wss://localhost:8443', 'localhost:8443'], - ['wss://localhost:443', 'localhost'], - ['ws://localhost:88', 'localhost:88'], - ['ws://localhost:80', 'localhost'] - ]; - - for (const [url, host] of variants) { - const ws = new WebSocket(url, options); - assert.strictEqual(ws._req.getHeader('host'), host); - } - }); - - it("doesn't add the origin header by default", (done) => { - const agent = new http.Agent(); - - agent.addRequest = (req) => { - assert.strictEqual(req.getHeader('origin'), undefined); - done(); - }; - - const ws = new WebSocket('ws://localhost', { agent }); - }); - - it('honors the `origin` option (1/2)', (done) => { - const agent = new http.Agent(); - - agent.addRequest = (req) => { - assert.strictEqual(req.getHeader('origin'), 'https://example.com:8000'); - done(); - }; - - const ws = new WebSocket('ws://localhost', { - origin: 'https://example.com:8000', - agent - }); - }); - - it('honors the `origin` option (2/2)', (done) => { - const agent = new http.Agent(); - - agent.addRequest = (req) => { - assert.strictEqual( - req.getHeader('sec-websocket-origin'), - 'https://example.com:8000' - ); - done(); - }; - - const ws = new WebSocket('ws://localhost', { - origin: 'https://example.com:8000', - protocolVersion: 8, - agent - }); - }); - - it('honors the `finishRequest` option', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const host = `localhost:${wss.address().port}`; - const ws = new WebSocket(`ws://${host}`, { - finishRequest(req, ws) { - assert.ok(req instanceof http.ClientRequest); - assert.strictEqual(req.getHeader('host'), host); - assert.ok(ws instanceof WebSocket); - assert.strictEqual(req, ws._req); - - req.on('socket', (socket) => { - socket.on('connect', () => { - req.setHeader('Cookie', 'foo=bar'); - req.end(); - }); - }); - } - }); - - ws.on('close', (code) => { - assert.strictEqual(code, 1005); - wss.close(done); - }); - }); - - wss.on('connection', (ws, req) => { - assert.strictEqual(req.headers.cookie, 'foo=bar'); - ws.close(); - }); - }); - }); - - describe('permessage-deflate', () => { - it('is enabled by default', (done) => { - const agent = new http.Agent(); - - agent.addRequest = (req) => { - assert.strictEqual( - req.getHeader('sec-websocket-extensions'), - 'permessage-deflate; client_max_window_bits' - ); - done(); - }; - - const ws = new WebSocket('ws://localhost', { agent }); - }); - - it('can be disabled', (done) => { - const agent = new http.Agent(); - - agent.addRequest = (req) => { - assert.strictEqual( - req.getHeader('sec-websocket-extensions'), - undefined - ); - done(); - }; - - const ws = new WebSocket('ws://localhost', { - perMessageDeflate: false, - agent - }); - }); - - it('can send extension parameters', (done) => { - const agent = new http.Agent(); - - const value = - 'permessage-deflate; server_no_context_takeover;' + - ' client_no_context_takeover; server_max_window_bits=10;' + - ' client_max_window_bits'; - - agent.addRequest = (req) => { - assert.strictEqual(req.getHeader('sec-websocket-extensions'), value); - done(); - }; - - const ws = new WebSocket('ws://localhost', { - perMessageDeflate: { - clientNoContextTakeover: true, - serverNoContextTakeover: true, - clientMaxWindowBits: true, - serverMaxWindowBits: 10 - }, - agent - }); - }); - - it('consumes all received data when connection is closed (1/2)', (done) => { - const wss = new WebSocket.Server( - { - perMessageDeflate: { threshold: 0 }, - port: 0 - }, - () => { - const messages = []; - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws._socket.on('close', () => { - assert.strictEqual(ws._receiver._state, 5); - }); - }); - - ws.on('message', (message, isBinary) => { - assert.ok(!isBinary); - messages.push(message.toString()); - }); - - ws.on('close', (code) => { - assert.strictEqual(code, 1006); - assert.deepStrictEqual(messages, ['foo', 'bar', 'baz', 'qux']); - wss.close(done); - }); - } - ); - - wss.on('connection', (ws) => { - ws.send('foo'); - ws.send('bar'); - ws.send('baz'); - ws.send('qux', () => ws._socket.end()); - }); - }); - - it('consumes all received data when connection is closed (2/2)', (done) => { - const wss = new WebSocket.Server( - { - perMessageDeflate: true, - port: 0 - }, - () => { - const messageLengths = []; - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('open', () => { - ws._socket.prependListener('close', () => { - assert.strictEqual(ws._receiver._state, 5); - assert.strictEqual(ws._socket._readableState.length, 3); - }); - - const push = ws._socket.push; - - // Override `ws._socket.push()` to know exactly when data is - // received and call `ws.terminate()` immediately after that without - // relying on a timer. - ws._socket.push = (data) => { - ws._socket.push = push; - ws._socket.push(data); - ws.terminate(); - }; - - const payload1 = Buffer.alloc(highWaterMark - 1024); - const payload2 = Buffer.alloc(1); - - const opts = { - fin: true, - opcode: 0x02, - mask: false, - readOnly: false - }; - - const list = [ - ...Sender.frame(payload1, { rsv1: false, ...opts }), - ...Sender.frame(payload2, { rsv1: true, ...opts }) - ]; - - for (let i = 0; i < 340; i++) { - list.push(list[list.length - 2], list[list.length - 1]); - } - - const data = Buffer.concat(list); - - assert.ok(data.length > highWaterMark); - - // This hack is used because there is no guarantee that more than - // `highWaterMark` bytes will be sent as a single TCP packet. - push.call(ws._socket, data); - - wss.clients - .values() - .next() - .value.send(payload2, { compress: false }); - }); - - ws.on('message', (message, isBinary) => { - assert.ok(isBinary); - messageLengths.push(message.length); - }); - - ws.on('close', (code) => { - assert.strictEqual(code, 1006); - assert.strictEqual(messageLengths.length, 343); - assert.strictEqual(messageLengths[0], highWaterMark - 1024); - assert.strictEqual(messageLengths[messageLengths.length - 1], 1); - wss.close(done); - }); - } - ); - }); - - it('handles a close frame received while compressing data', (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._receiver.on('conclude', () => { - assert.ok(ws._sender._deflating); - }); - - ws.send('foo'); - ws.send('bar'); - ws.send('baz'); - ws.send('qux'); - }); - } - ); - - wss.on('connection', (ws) => { - const messages = []; - - ws.on('message', (message, isBinary) => { - assert.ok(!isBinary); - messages.push(message.toString()); - }); - - ws.on('close', (code, reason) => { - assert.deepStrictEqual(messages, ['foo', 'bar', 'baz', 'qux']); - assert.strictEqual(code, 1000); - assert.deepStrictEqual(reason, EMPTY_BUFFER); - wss.close(done); - }); - - ws.close(1000); - }); - }); - - describe('#close', () => { - it('can be used while data is being decompressed', (done) => { - const wss = new WebSocket.Server( - { - perMessageDeflate: true, - port: 0 - }, - () => { - const messages = []; - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - - ws.on('message', (message, isBinary) => { - assert.ok(!isBinary); - - if (messages.push(message.toString()) > 1) return; - - // `queueMicrotask()` is not available in Node.js < 11. - Promise.resolve().then(() => { - process.nextTick(() => { - assert.strictEqual(ws._receiver._state, 5); - ws.close(1000); - }); - }); - }); - - ws.on('close', (code, reason) => { - assert.deepStrictEqual(messages, ['', '', '', '']); - assert.strictEqual(code, 1000); - assert.deepStrictEqual(reason, EMPTY_BUFFER); - wss.close(done); - }); - } - ); - - wss.on('connection', (ws) => { - const buf = Buffer.from('c10100c10100c10100c10100', 'hex'); - ws._socket.write(buf); - }); - }); - }); - - describe('#send', () => { - it('can send text data', (done) => { - const wss = new WebSocket.Server( - { - perMessageDeflate: { threshold: 0 }, - port: 0 - }, - () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: { threshold: 0 } - }); - - ws.on('open', () => { - ws.send('hi', { compress: true }); - ws.close(); - }); - - ws.on('message', (message, isBinary) => { - assert.deepStrictEqual(message, Buffer.from('hi')); - assert.ok(!isBinary); - wss.close(done); - }); - } - ); - - wss.on('connection', (ws) => { - ws.on('message', (message, isBinary) => { - ws.send(message, { binary: isBinary, compress: true }); - }); - }); - }); - - it('can send a `TypedArray`', (done) => { - const array = new Float32Array(5); - - for (let i = 0; i < array.length; i++) { - array[i] = i / 2; - } - - const wss = new WebSocket.Server( - { - perMessageDeflate: { threshold: 0 }, - port: 0 - }, - () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: { threshold: 0 } - }); - - ws.on('open', () => { - ws.send(array, { compress: true }); - ws.close(); - }); - - ws.on('message', (message, isBinary) => { - assert.deepStrictEqual(message, Buffer.from(array.buffer)); - assert.ok(isBinary); - wss.close(done); - }); - } - ); - - wss.on('connection', (ws) => { - ws.on('message', (message, isBinary) => { - assert.ok(isBinary); - ws.send(message, { compress: true }); - }); - }); - }); - - it('can send an `ArrayBuffer`', (done) => { - const array = new Float32Array(5); - - for (let i = 0; i < array.length; i++) { - array[i] = i / 2; - } - - const wss = new WebSocket.Server( - { - perMessageDeflate: { threshold: 0 }, - port: 0 - }, - () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: { threshold: 0 } - }); - - ws.on('open', () => { - ws.send(array.buffer, { compress: true }); - ws.close(); - }); - - ws.on('message', (message, isBinary) => { - assert.deepStrictEqual(message, Buffer.from(array.buffer)); - assert.ok(isBinary); - wss.close(done); - }); - } - ); - - wss.on('connection', (ws) => { - ws.on('message', (message, isBinary) => { - assert.ok(isBinary); - ws.send(message, { compress: true }); - }); - }); - }); - - it('ignores the `compress` option if the extension is disabled', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: false - }); - - ws.on('open', () => { - ws.send('hi', { compress: true }); - ws.close(); - }); - - ws.on('message', (message, isBinary) => { - assert.deepStrictEqual(message, Buffer.from('hi')); - assert.ok(!isBinary); - wss.close(done); - }); - }); - - wss.on('connection', (ws) => { - ws.on('message', (message, isBinary) => { - ws.send(message, { binary: isBinary, compress: true }); - }); - }); - }); - - it('calls the callback if the socket is closed prematurely', (done) => { - const called = []; - 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'); - ws.send('bar', (err) => { - called.push(1); - - assert.strictEqual(ws.readyState, WebSocket.CLOSING); - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'The socket was closed while data was being compressed' - ); - }); - ws.send('baz'); - ws.send('qux', (err) => { - called.push(2); - - assert.strictEqual(ws.readyState, WebSocket.CLOSING); - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'The socket was closed while data was being compressed' - ); - }); - }); - } - ); - - wss.on('connection', (ws) => { - ws.on('close', () => { - assert.deepStrictEqual(called, [1, 2]); - wss.close(done); - }); - - ws._socket.end(); - }); - }); - }); - - describe('#terminate', () => { - it('can be used while data is being compressed', (done) => { - const wss = new WebSocket.Server( - { - perMessageDeflate: { threshold: 0 }, - port: 0 - }, - () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`, { - perMessageDeflate: { threshold: 0 } - }); - - ws.on('open', () => { - ws.send('hi', (err) => { - assert.strictEqual(ws.readyState, WebSocket.CLOSING); - assert.ok(err instanceof Error); - assert.strictEqual( - err.message, - 'The socket was closed while data was being compressed' - ); - - ws.on('close', () => { - wss.close(done); - }); - }); - ws.terminate(); - }); - } - ); - }); - - it('can be used while data is being decompressed', (done) => { - const wss = new WebSocket.Server( - { - perMessageDeflate: true, - port: 0 - }, - () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - const messages = []; - - ws.on('message', (message, isBinary) => { - assert.ok(!isBinary); - - if (messages.push(message.toString()) > 1) return; - - // `queueMicrotask()` is not available in Node.js < 11. - Promise.resolve().then(() => { - process.nextTick(() => { - assert.strictEqual(ws._receiver._state, 5); - ws.terminate(); - }); - }); - }); - - ws.on('close', (code, reason) => { - assert.deepStrictEqual(messages, ['', '', '', '']); - assert.strictEqual(code, 1006); - assert.strictEqual(reason, EMPTY_BUFFER); - wss.close(done); - }); - } - ); - - wss.on('connection', (ws) => { - const buf = Buffer.from('c10100c10100c10100c10100', 'hex'); - ws._socket.write(buf); - }); - }); - }); - }); - - describe('Connection close', () => { - it('closes cleanly after simultaneous errors (1/2)', (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); - }); - }); - - ws.on('open', () => { - // Write an invalid frame in both directions to trigger simultaneous - // failure. - const chunk = Buffer.from([0x85, 0x00]); - - wss.clients.values().next().value._socket.write(chunk); - ws._socket.write(chunk); - }); - }); - - wss.on('connection', (ws) => { - 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); - - serverClientCloseEventEmitted = true; - if (clientCloseEventEmitted) wss.close(done); - }); - }); - }); - }); - - it('closes cleanly after simultaneous errors (2/2)', (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); - }); - }); - - ws.on('open', () => { - // Write an invalid frame in both directions and change the - // `readyState` to `WebSocket.CLOSING`. - const chunk = Buffer.from([0x85, 0x00]); - const serverWs = wss.clients.values().next().value; - - serverWs._socket.write(chunk); - serverWs.close(); - - ws._socket.write(chunk); - ws.close(); - }); - }); - - wss.on('connection', (ws) => { - 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); - - serverClientCloseEventEmitted = true; - if (clientCloseEventEmitted) wss.close(done); - }); - }); - }); - }); - - it('resumes the socket when an error occurs', (done) => { - const maxPayload = 16 * 1024; - const wss = new WebSocket.Server({ maxPayload, port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - }); - - wss.on('connection', (ws) => { - const list = [ - ...Sender.frame(Buffer.alloc(maxPayload + 1), { - fin: true, - opcode: 0x02, - mask: true, - readOnly: false - }) - ]; - - ws.on('error', (err) => { - assert.ok(err instanceof RangeError); - assert.strictEqual(err.code, 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'); - assert.strictEqual(err.message, 'Max payload size exceeded'); - - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1006); - assert.strictEqual(reason, EMPTY_BUFFER); - wss.close(done); - }); - }); - - ws._socket.push(Buffer.concat(list)); - }); - }); - - it('resumes the socket when the close frame is received', (done) => { - const wss = new WebSocket.Server({ port: 0 }, () => { - const ws = new WebSocket(`ws://localhost:${wss.address().port}`); - }); - - wss.on('connection', (ws) => { - const opts = { fin: true, mask: true, readOnly: false }; - const list = [ - ...Sender.frame(Buffer.alloc(16 * 1024), { opcode: 0x02, ...opts }), - ...Sender.frame(EMPTY_BUFFER, { opcode: 0x08, ...opts }) - ]; - - ws.on('close', (code, reason) => { - assert.strictEqual(code, 1005); - assert.strictEqual(reason, EMPTY_BUFFER); - wss.close(done); - }); - - ws._socket.push(Buffer.concat(list)); - }); - }); - }); - */ -}); \ No newline at end of file diff --git a/lib/__tests__/unit/ws.unit.test.ts b/lib/__tests__/unit/ws.unit.test.ts new file mode 100644 index 0000000..6f16daf --- /dev/null +++ b/lib/__tests__/unit/ws.unit.test.ts @@ -0,0 +1,27 @@ +'use strict'; +import { WebSocket } from 'ws'; + +WebSocket.EventEmitter; +beforeAll((done) => { + done(); +}); + +describe('WebSocket checks', () => { + describe('WebSocket invalid url checks', () => { + it('throws an error when using an invalid url', () => { + + expect(() => new WebSocket('typo')).toThrow(SyntaxError); + + expect(() => new WebSocket('invalid://websocket-echo.com')).toThrow(new Error('The URL\'s protocol must be one of "ws:", "wss:", or "ws+unix:"')); + + // URL's pathname is empty + expect(() => new WebSocket('ws+unix:')).toThrow(SyntaxError); + + expect(() => new WebSocket('wss://websocket-echo.com#foo')).toThrow(/The URL contains a fragment identifier/); + }); + + afterAll((done) => { + done(); + }); + }); +}); \ No newline at end of file