| |
| |
| 'use strict'; |
|
|
| const EventEmitter = require('events'); |
| const https = require('https'); |
| const http = require('http'); |
| const net = require('net'); |
| const tls = require('tls'); |
| const { randomBytes, createHash } = require('crypto'); |
| const { Duplex, Readable } = require('stream'); |
| const { URL } = require('url'); |
|
|
| const PerMessageDeflate = require('./permessage-deflate'); |
| const Receiver = require('./receiver'); |
| const Sender = require('./sender'); |
| const { isBlob } = require('./validation'); |
|
|
| const { |
| BINARY_TYPES, |
| CLOSE_TIMEOUT, |
| EMPTY_BUFFER, |
| GUID, |
| kForOnEventAttribute, |
| kListener, |
| kStatusCode, |
| kWebSocket, |
| NOOP |
| } = require('./constants'); |
| const { |
| EventTarget: { addEventListener, removeEventListener } |
| } = require('./event-target'); |
| const { format, parse } = require('./extension'); |
| const { toBuffer } = require('./buffer-util'); |
|
|
| const kAborted = Symbol('kAborted'); |
| const protocolVersions = [8, 13]; |
| const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; |
| const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/; |
|
|
| |
| |
| |
| |
| |
| class WebSocket extends EventEmitter { |
| |
| |
| |
| |
| |
| |
| |
| constructor(address, protocols, options) { |
| super(); |
|
|
| this._binaryType = BINARY_TYPES[0]; |
| this._closeCode = 1006; |
| this._closeFrameReceived = false; |
| this._closeFrameSent = false; |
| this._closeMessage = EMPTY_BUFFER; |
| this._closeTimer = null; |
| this._errorEmitted = false; |
| this._extensions = {}; |
| this._paused = false; |
| this._protocol = ''; |
| this._readyState = WebSocket.CONNECTING; |
| this._receiver = null; |
| this._sender = null; |
| this._socket = null; |
|
|
| if (address !== null) { |
| this._bufferedAmount = 0; |
| this._isServer = false; |
| this._redirects = 0; |
|
|
| if (protocols === undefined) { |
| protocols = []; |
| } else if (!Array.isArray(protocols)) { |
| if (typeof protocols === 'object' && protocols !== null) { |
| options = protocols; |
| protocols = []; |
| } else { |
| protocols = [protocols]; |
| } |
| } |
|
|
| initAsClient(this, address, protocols, options); |
| } else { |
| this._autoPong = options.autoPong; |
| this._closeTimeout = options.closeTimeout; |
| this._isServer = true; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| get binaryType() { |
| return this._binaryType; |
| } |
|
|
| set binaryType(type) { |
| if (!BINARY_TYPES.includes(type)) return; |
|
|
| this._binaryType = type; |
|
|
| |
| |
| |
| if (this._receiver) this._receiver._binaryType = type; |
| } |
|
|
| |
| |
| |
| get bufferedAmount() { |
| if (!this._socket) return this._bufferedAmount; |
|
|
| return this._socket._writableState.length + this._sender._bufferedBytes; |
| } |
|
|
| |
| |
| |
| get extensions() { |
| return Object.keys(this._extensions).join(); |
| } |
|
|
| |
| |
| |
| get isPaused() { |
| return this._paused; |
| } |
|
|
| |
| |
| |
| |
| get onclose() { |
| return null; |
| } |
|
|
| |
| |
| |
| |
| get onerror() { |
| return null; |
| } |
|
|
| |
| |
| |
| |
| get onopen() { |
| return null; |
| } |
|
|
| |
| |
| |
| |
| get onmessage() { |
| return null; |
| } |
|
|
| |
| |
| |
| get protocol() { |
| return this._protocol; |
| } |
|
|
| |
| |
| |
| get readyState() { |
| return this._readyState; |
| } |
|
|
| |
| |
| |
| get url() { |
| return this._url; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| setSocket(socket, head, options) { |
| const receiver = new Receiver({ |
| allowSynchronousEvents: options.allowSynchronousEvents, |
| binaryType: this.binaryType, |
| extensions: this._extensions, |
| isServer: this._isServer, |
| maxBufferedChunks: options.maxBufferedChunks, |
| maxFragments: options.maxFragments, |
| maxPayload: options.maxPayload, |
| skipUTF8Validation: options.skipUTF8Validation |
| }); |
|
|
| const sender = new Sender(socket, this._extensions, options.generateMask); |
|
|
| this._receiver = receiver; |
| this._sender = sender; |
| this._socket = socket; |
|
|
| receiver[kWebSocket] = this; |
| sender[kWebSocket] = this; |
| socket[kWebSocket] = this; |
|
|
| receiver.on('conclude', receiverOnConclude); |
| receiver.on('drain', receiverOnDrain); |
| receiver.on('error', receiverOnError); |
| receiver.on('message', receiverOnMessage); |
| receiver.on('ping', receiverOnPing); |
| receiver.on('pong', receiverOnPong); |
|
|
| sender.onerror = senderOnError; |
|
|
| |
| |
| |
| if (socket.setTimeout) socket.setTimeout(0); |
| if (socket.setNoDelay) socket.setNoDelay(); |
|
|
| if (head.length > 0) socket.unshift(head); |
|
|
| socket.on('close', socketOnClose); |
| socket.on('data', socketOnData); |
| socket.on('end', socketOnEnd); |
| socket.on('error', socketOnError); |
|
|
| this._readyState = WebSocket.OPEN; |
| this.emit('open'); |
| } |
|
|
| |
| |
| |
| |
| |
| emitClose() { |
| if (!this._socket) { |
| this._readyState = WebSocket.CLOSED; |
| this.emit('close', this._closeCode, this._closeMessage); |
| return; |
| } |
|
|
| if (this._extensions[PerMessageDeflate.extensionName]) { |
| this._extensions[PerMessageDeflate.extensionName].cleanup(); |
| } |
|
|
| this._receiver.removeAllListeners(); |
| this._readyState = WebSocket.CLOSED; |
| this.emit('close', this._closeCode, this._closeMessage); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| close(code, data) { |
| if (this.readyState === WebSocket.CLOSED) return; |
| if (this.readyState === WebSocket.CONNECTING) { |
| const msg = 'WebSocket was closed before the connection was established'; |
| abortHandshake(this, this._req, msg); |
| return; |
| } |
|
|
| if (this.readyState === WebSocket.CLOSING) { |
| if ( |
| this._closeFrameSent && |
| (this._closeFrameReceived || this._receiver._writableState.errorEmitted) |
| ) { |
| this._socket.end(); |
| } |
|
|
| return; |
| } |
|
|
| this._readyState = WebSocket.CLOSING; |
| this._sender.close(code, data, !this._isServer, (err) => { |
| |
| |
| |
| |
| if (err) return; |
|
|
| this._closeFrameSent = true; |
|
|
| if ( |
| this._closeFrameReceived || |
| this._receiver._writableState.errorEmitted |
| ) { |
| this._socket.end(); |
| } |
| }); |
|
|
| setCloseTimer(this); |
| } |
|
|
| |
| |
| |
| |
| |
| pause() { |
| if ( |
| this.readyState === WebSocket.CONNECTING || |
| this.readyState === WebSocket.CLOSED |
| ) { |
| return; |
| } |
|
|
| this._paused = true; |
| this._socket.pause(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| ping(data, mask, cb) { |
| if (this.readyState === WebSocket.CONNECTING) { |
| throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); |
| } |
|
|
| if (typeof data === 'function') { |
| cb = data; |
| data = mask = undefined; |
| } else if (typeof mask === 'function') { |
| cb = mask; |
| mask = undefined; |
| } |
|
|
| if (typeof data === 'number') data = data.toString(); |
|
|
| if (this.readyState !== WebSocket.OPEN) { |
| sendAfterClose(this, data, cb); |
| return; |
| } |
|
|
| if (mask === undefined) mask = !this._isServer; |
| this._sender.ping(data || EMPTY_BUFFER, mask, cb); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| pong(data, mask, cb) { |
| if (this.readyState === WebSocket.CONNECTING) { |
| throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); |
| } |
|
|
| if (typeof data === 'function') { |
| cb = data; |
| data = mask = undefined; |
| } else if (typeof mask === 'function') { |
| cb = mask; |
| mask = undefined; |
| } |
|
|
| if (typeof data === 'number') data = data.toString(); |
|
|
| if (this.readyState !== WebSocket.OPEN) { |
| sendAfterClose(this, data, cb); |
| return; |
| } |
|
|
| if (mask === undefined) mask = !this._isServer; |
| this._sender.pong(data || EMPTY_BUFFER, mask, cb); |
| } |
|
|
| |
| |
| |
| |
| |
| resume() { |
| if ( |
| this.readyState === WebSocket.CONNECTING || |
| this.readyState === WebSocket.CLOSED |
| ) { |
| return; |
| } |
|
|
| this._paused = false; |
| if (!this._receiver._writableState.needDrain) this._socket.resume(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| send(data, options, cb) { |
| if (this.readyState === WebSocket.CONNECTING) { |
| throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); |
| } |
|
|
| if (typeof options === 'function') { |
| cb = options; |
| options = {}; |
| } |
|
|
| if (typeof data === 'number') data = data.toString(); |
|
|
| if (this.readyState !== WebSocket.OPEN) { |
| sendAfterClose(this, data, cb); |
| return; |
| } |
|
|
| const opts = { |
| binary: typeof data !== 'string', |
| mask: !this._isServer, |
| compress: true, |
| fin: true, |
| ...options |
| }; |
|
|
| if (!this._extensions[PerMessageDeflate.extensionName]) { |
| opts.compress = false; |
| } |
|
|
| this._sender.send(data || EMPTY_BUFFER, opts, cb); |
| } |
|
|
| |
| |
| |
| |
| |
| terminate() { |
| if (this.readyState === WebSocket.CLOSED) return; |
| if (this.readyState === WebSocket.CONNECTING) { |
| const msg = 'WebSocket was closed before the connection was established'; |
| abortHandshake(this, this._req, msg); |
| return; |
| } |
|
|
| if (this._socket) { |
| this._readyState = WebSocket.CLOSING; |
| this._socket.destroy(); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| Object.defineProperty(WebSocket, 'CONNECTING', { |
| enumerable: true, |
| value: readyStates.indexOf('CONNECTING') |
| }); |
|
|
| |
| |
| |
| |
| Object.defineProperty(WebSocket.prototype, 'CONNECTING', { |
| enumerable: true, |
| value: readyStates.indexOf('CONNECTING') |
| }); |
|
|
| |
| |
| |
| |
| Object.defineProperty(WebSocket, 'OPEN', { |
| enumerable: true, |
| value: readyStates.indexOf('OPEN') |
| }); |
|
|
| |
| |
| |
| |
| Object.defineProperty(WebSocket.prototype, 'OPEN', { |
| enumerable: true, |
| value: readyStates.indexOf('OPEN') |
| }); |
|
|
| |
| |
| |
| |
| Object.defineProperty(WebSocket, 'CLOSING', { |
| enumerable: true, |
| value: readyStates.indexOf('CLOSING') |
| }); |
|
|
| |
| |
| |
| |
| Object.defineProperty(WebSocket.prototype, 'CLOSING', { |
| enumerable: true, |
| value: readyStates.indexOf('CLOSING') |
| }); |
|
|
| |
| |
| |
| |
| Object.defineProperty(WebSocket, 'CLOSED', { |
| enumerable: true, |
| value: readyStates.indexOf('CLOSED') |
| }); |
|
|
| |
| |
| |
| |
| Object.defineProperty(WebSocket.prototype, 'CLOSED', { |
| enumerable: true, |
| value: readyStates.indexOf('CLOSED') |
| }); |
|
|
| [ |
| 'binaryType', |
| 'bufferedAmount', |
| 'extensions', |
| 'isPaused', |
| 'protocol', |
| 'readyState', |
| 'url' |
| ].forEach((property) => { |
| Object.defineProperty(WebSocket.prototype, property, { enumerable: true }); |
| }); |
|
|
| |
| |
| |
| |
| ['open', 'error', 'close', 'message'].forEach((method) => { |
| Object.defineProperty(WebSocket.prototype, `on${method}`, { |
| enumerable: true, |
| get() { |
| for (const listener of this.listeners(method)) { |
| if (listener[kForOnEventAttribute]) return listener[kListener]; |
| } |
|
|
| return null; |
| }, |
| set(handler) { |
| for (const listener of this.listeners(method)) { |
| if (listener[kForOnEventAttribute]) { |
| this.removeListener(method, listener); |
| break; |
| } |
| } |
|
|
| if (typeof handler !== 'function') return; |
|
|
| this.addEventListener(method, handler, { |
| [kForOnEventAttribute]: true |
| }); |
| } |
| }); |
| }); |
|
|
| WebSocket.prototype.addEventListener = addEventListener; |
| WebSocket.prototype.removeEventListener = removeEventListener; |
|
|
| module.exports = WebSocket; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function initAsClient(websocket, address, protocols, options) { |
| const opts = { |
| allowSynchronousEvents: true, |
| autoPong: true, |
| closeTimeout: CLOSE_TIMEOUT, |
| protocolVersion: protocolVersions[1], |
| maxBufferedChunks: 1024 * 1024, |
| maxFragments: 128 * 1024, |
| maxPayload: 100 * 1024 * 1024, |
| skipUTF8Validation: false, |
| perMessageDeflate: true, |
| followRedirects: false, |
| maxRedirects: 10, |
| ...options, |
| socketPath: undefined, |
| hostname: undefined, |
| protocol: undefined, |
| timeout: undefined, |
| method: 'GET', |
| host: undefined, |
| path: undefined, |
| port: undefined |
| }; |
|
|
| websocket._autoPong = opts.autoPong; |
| websocket._closeTimeout = opts.closeTimeout; |
|
|
| if (!protocolVersions.includes(opts.protocolVersion)) { |
| throw new RangeError( |
| `Unsupported protocol version: ${opts.protocolVersion} ` + |
| `(supported versions: ${protocolVersions.join(', ')})` |
| ); |
| } |
|
|
| let parsedUrl; |
|
|
| if (address instanceof URL) { |
| parsedUrl = address; |
| } else { |
| try { |
| parsedUrl = new URL(address); |
| } catch { |
| throw new SyntaxError(`Invalid URL: ${address}`); |
| } |
| } |
|
|
| if (parsedUrl.protocol === 'http:') { |
| parsedUrl.protocol = 'ws:'; |
| } else if (parsedUrl.protocol === 'https:') { |
| parsedUrl.protocol = 'wss:'; |
| } |
|
|
| websocket._url = parsedUrl.href; |
|
|
| const isSecure = parsedUrl.protocol === 'wss:'; |
| const isIpcUrl = parsedUrl.protocol === 'ws+unix:'; |
| let invalidUrlMessage; |
|
|
| if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) { |
| invalidUrlMessage = |
| 'The URL\'s protocol must be one of "ws:", "wss:", ' + |
| '"http:", "https:", or "ws+unix:"'; |
| } else if (isIpcUrl && !parsedUrl.pathname) { |
| invalidUrlMessage = "The URL's pathname is empty"; |
| } else if (parsedUrl.hash) { |
| invalidUrlMessage = 'The URL contains a fragment identifier'; |
| } |
|
|
| if (invalidUrlMessage) { |
| const err = new SyntaxError(invalidUrlMessage); |
|
|
| if (websocket._redirects === 0) { |
| throw err; |
| } else { |
| emitErrorAndClose(websocket, err); |
| return; |
| } |
| } |
|
|
| const defaultPort = isSecure ? 443 : 80; |
| const key = randomBytes(16).toString('base64'); |
| const request = isSecure ? https.request : http.request; |
| const protocolSet = new Set(); |
| let perMessageDeflate; |
|
|
| opts.createConnection = |
| opts.createConnection || (isSecure ? tlsConnect : netConnect); |
| opts.defaultPort = opts.defaultPort || defaultPort; |
| opts.port = parsedUrl.port || defaultPort; |
| opts.host = parsedUrl.hostname.startsWith('[') |
| ? parsedUrl.hostname.slice(1, -1) |
| : parsedUrl.hostname; |
| opts.headers = { |
| ...opts.headers, |
| 'Sec-WebSocket-Version': opts.protocolVersion, |
| 'Sec-WebSocket-Key': key, |
| Connection: 'Upgrade', |
| Upgrade: 'websocket' |
| }; |
| opts.path = parsedUrl.pathname + parsedUrl.search; |
| opts.timeout = opts.handshakeTimeout; |
|
|
| if (opts.perMessageDeflate) { |
| perMessageDeflate = new PerMessageDeflate({ |
| ...opts.perMessageDeflate, |
| isServer: false, |
| maxPayload: opts.maxPayload |
| }); |
| opts.headers['Sec-WebSocket-Extensions'] = format({ |
| [PerMessageDeflate.extensionName]: perMessageDeflate.offer() |
| }); |
| } |
| if (protocols.length) { |
| for (const protocol of protocols) { |
| if ( |
| typeof protocol !== 'string' || |
| !subprotocolRegex.test(protocol) || |
| protocolSet.has(protocol) |
| ) { |
| throw new SyntaxError( |
| 'An invalid or duplicated subprotocol was specified' |
| ); |
| } |
|
|
| protocolSet.add(protocol); |
| } |
|
|
| opts.headers['Sec-WebSocket-Protocol'] = protocols.join(','); |
| } |
| if (opts.origin) { |
| if (opts.protocolVersion < 13) { |
| opts.headers['Sec-WebSocket-Origin'] = opts.origin; |
| } else { |
| opts.headers.Origin = opts.origin; |
| } |
| } |
| if (parsedUrl.username || parsedUrl.password) { |
| opts.auth = `${parsedUrl.username}:${parsedUrl.password}`; |
| } |
|
|
| if (isIpcUrl) { |
| const parts = opts.path.split(':'); |
|
|
| opts.socketPath = parts[0]; |
| opts.path = parts[1]; |
| } |
|
|
| let req; |
|
|
| if (opts.followRedirects) { |
| if (websocket._redirects === 0) { |
| websocket._originalIpc = isIpcUrl; |
| websocket._originalSecure = isSecure; |
| websocket._originalHostOrSocketPath = isIpcUrl |
| ? opts.socketPath |
| : parsedUrl.host; |
|
|
| const headers = options && options.headers; |
|
|
| |
| |
| |
| |
| options = { ...options, headers: {} }; |
|
|
| if (headers) { |
| for (const [key, value] of Object.entries(headers)) { |
| options.headers[key.toLowerCase()] = value; |
| } |
| } |
| } else if (websocket.listenerCount('redirect') === 0) { |
| const isSameHost = isIpcUrl |
| ? websocket._originalIpc |
| ? opts.socketPath === websocket._originalHostOrSocketPath |
| : false |
| : websocket._originalIpc |
| ? false |
| : parsedUrl.host === websocket._originalHostOrSocketPath; |
|
|
| if (!isSameHost || (websocket._originalSecure && !isSecure)) { |
| |
| |
| |
| |
| delete opts.headers.authorization; |
| delete opts.headers.cookie; |
|
|
| if (!isSameHost) delete opts.headers.host; |
|
|
| opts.auth = undefined; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| if (opts.auth && !options.headers.authorization) { |
| options.headers.authorization = |
| 'Basic ' + Buffer.from(opts.auth).toString('base64'); |
| } |
|
|
| req = websocket._req = request(opts); |
|
|
| if (websocket._redirects) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| websocket.emit('redirect', websocket.url, req); |
| } |
| } else { |
| req = websocket._req = request(opts); |
| } |
|
|
| if (opts.timeout) { |
| req.on('timeout', () => { |
| abortHandshake(websocket, req, 'Opening handshake has timed out'); |
| }); |
| } |
|
|
| req.on('error', (err) => { |
| if (req === null || req[kAborted]) return; |
|
|
| req = websocket._req = null; |
| emitErrorAndClose(websocket, err); |
| }); |
|
|
| req.on('response', (res) => { |
| const location = res.headers.location; |
| const statusCode = res.statusCode; |
|
|
| if ( |
| location && |
| opts.followRedirects && |
| statusCode >= 300 && |
| statusCode < 400 |
| ) { |
| if (++websocket._redirects > opts.maxRedirects) { |
| abortHandshake(websocket, req, 'Maximum redirects exceeded'); |
| return; |
| } |
|
|
| req.abort(); |
|
|
| let addr; |
|
|
| try { |
| addr = new URL(location, address); |
| } catch (e) { |
| const err = new SyntaxError(`Invalid URL: ${location}`); |
| emitErrorAndClose(websocket, err); |
| return; |
| } |
|
|
| initAsClient(websocket, addr, protocols, options); |
| } else if (!websocket.emit('unexpected-response', req, res)) { |
| abortHandshake( |
| websocket, |
| req, |
| `Unexpected server response: ${res.statusCode}` |
| ); |
| } |
| }); |
|
|
| req.on('upgrade', (res, socket, head) => { |
| websocket.emit('upgrade', res); |
|
|
| |
| |
| |
| |
| if (websocket.readyState !== WebSocket.CONNECTING) return; |
|
|
| req = websocket._req = null; |
|
|
| const upgrade = res.headers.upgrade; |
|
|
| if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') { |
| abortHandshake(websocket, socket, 'Invalid Upgrade header'); |
| return; |
| } |
|
|
| const digest = createHash('sha1') |
| .update(key + GUID) |
| .digest('base64'); |
|
|
| if (res.headers['sec-websocket-accept'] !== digest) { |
| abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header'); |
| return; |
| } |
|
|
| const serverProt = res.headers['sec-websocket-protocol']; |
| let protError; |
|
|
| if (serverProt !== undefined) { |
| if (!protocolSet.size) { |
| protError = 'Server sent a subprotocol but none was requested'; |
| } else if (!protocolSet.has(serverProt)) { |
| protError = 'Server sent an invalid subprotocol'; |
| } |
| } else if (protocolSet.size) { |
| protError = 'Server sent no subprotocol'; |
| } |
|
|
| if (protError) { |
| abortHandshake(websocket, socket, protError); |
| return; |
| } |
|
|
| if (serverProt) websocket._protocol = serverProt; |
|
|
| const secWebSocketExtensions = res.headers['sec-websocket-extensions']; |
|
|
| if (secWebSocketExtensions !== undefined) { |
| if (!perMessageDeflate) { |
| const message = |
| 'Server sent a Sec-WebSocket-Extensions header but no extension ' + |
| 'was requested'; |
| abortHandshake(websocket, socket, message); |
| return; |
| } |
|
|
| let extensions; |
|
|
| try { |
| extensions = parse(secWebSocketExtensions); |
| } catch (err) { |
| const message = 'Invalid Sec-WebSocket-Extensions header'; |
| abortHandshake(websocket, socket, message); |
| return; |
| } |
|
|
| const extensionNames = Object.keys(extensions); |
|
|
| if ( |
| extensionNames.length !== 1 || |
| extensionNames[0] !== PerMessageDeflate.extensionName |
| ) { |
| const message = 'Server indicated an extension that was not requested'; |
| abortHandshake(websocket, socket, message); |
| return; |
| } |
|
|
| try { |
| perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]); |
| } catch (err) { |
| const message = 'Invalid Sec-WebSocket-Extensions header'; |
| abortHandshake(websocket, socket, message); |
| return; |
| } |
|
|
| websocket._extensions[PerMessageDeflate.extensionName] = |
| perMessageDeflate; |
| } |
|
|
| websocket.setSocket(socket, head, { |
| allowSynchronousEvents: opts.allowSynchronousEvents, |
| generateMask: opts.generateMask, |
| maxBufferedChunks: opts.maxBufferedChunks, |
| maxFragments: opts.maxFragments, |
| maxPayload: opts.maxPayload, |
| skipUTF8Validation: opts.skipUTF8Validation |
| }); |
| }); |
|
|
| if (opts.finishRequest) { |
| opts.finishRequest(req, websocket); |
| } else { |
| req.end(); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function emitErrorAndClose(websocket, err) { |
| websocket._readyState = WebSocket.CLOSING; |
| |
| |
| |
| |
| websocket._errorEmitted = true; |
| websocket.emit('error', err); |
| websocket.emitClose(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function netConnect(options) { |
| options.path = options.socketPath; |
| return net.connect(options); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function tlsConnect(options) { |
| options.path = undefined; |
|
|
| if (!options.servername && options.servername !== '') { |
| options.servername = net.isIP(options.host) ? '' : options.host; |
| } |
|
|
| return tls.connect(options); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function abortHandshake(websocket, stream, message) { |
| websocket._readyState = WebSocket.CLOSING; |
|
|
| const err = new Error(message); |
| Error.captureStackTrace(err, abortHandshake); |
|
|
| if (stream.setHeader) { |
| stream[kAborted] = true; |
| stream.abort(); |
|
|
| if (stream.socket && !stream.socket.destroyed) { |
| |
| |
| |
| |
| |
| stream.socket.destroy(); |
| } |
|
|
| process.nextTick(emitErrorAndClose, websocket, err); |
| } else { |
| stream.destroy(err); |
| stream.once('error', websocket.emit.bind(websocket, 'error')); |
| stream.once('close', websocket.emitClose.bind(websocket)); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function sendAfterClose(websocket, data, cb) { |
| if (data) { |
| const length = isBlob(data) ? data.size : toBuffer(data).length; |
|
|
| |
| |
| |
| |
| |
| |
| if (websocket._socket) websocket._sender._bufferedBytes += length; |
| else websocket._bufferedAmount += length; |
| } |
|
|
| if (cb) { |
| const err = new Error( |
| `WebSocket is not open: readyState ${websocket.readyState} ` + |
| `(${readyStates[websocket.readyState]})` |
| ); |
| process.nextTick(cb, err); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function receiverOnConclude(code, reason) { |
| const websocket = this[kWebSocket]; |
|
|
| websocket._closeFrameReceived = true; |
| websocket._closeMessage = reason; |
| websocket._closeCode = code; |
|
|
| if (websocket._socket[kWebSocket] === undefined) return; |
|
|
| websocket._socket.removeListener('data', socketOnData); |
| process.nextTick(resume, websocket._socket); |
|
|
| if (code === 1005) websocket.close(); |
| else websocket.close(code, reason); |
| } |
|
|
| |
| |
| |
| |
| |
| function receiverOnDrain() { |
| const websocket = this[kWebSocket]; |
|
|
| if (!websocket.isPaused) websocket._socket.resume(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function receiverOnError(err) { |
| const websocket = this[kWebSocket]; |
|
|
| if (websocket._socket[kWebSocket] !== undefined) { |
| websocket._socket.removeListener('data', socketOnData); |
|
|
| |
| |
| |
| |
| process.nextTick(resume, websocket._socket); |
|
|
| websocket.close(err[kStatusCode]); |
| } |
|
|
| if (!websocket._errorEmitted) { |
| websocket._errorEmitted = true; |
| websocket.emit('error', err); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function receiverOnFinish() { |
| this[kWebSocket].emitClose(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function receiverOnMessage(data, isBinary) { |
| this[kWebSocket].emit('message', data, isBinary); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function receiverOnPing(data) { |
| const websocket = this[kWebSocket]; |
|
|
| if (websocket._autoPong) websocket.pong(data, !this._isServer, NOOP); |
| websocket.emit('ping', data); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function receiverOnPong(data) { |
| this[kWebSocket].emit('pong', data); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function resume(stream) { |
| stream.resume(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function senderOnError(err) { |
| const websocket = this[kWebSocket]; |
|
|
| if (websocket.readyState === WebSocket.CLOSED) return; |
| if (websocket.readyState === WebSocket.OPEN) { |
| websocket._readyState = WebSocket.CLOSING; |
| setCloseTimer(websocket); |
| } |
|
|
| |
| |
| |
| |
| |
| this._socket.end(); |
|
|
| if (!websocket._errorEmitted) { |
| websocket._errorEmitted = true; |
| websocket.emit('error', err); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function setCloseTimer(websocket) { |
| websocket._closeTimer = setTimeout( |
| websocket._socket.destroy.bind(websocket._socket), |
| websocket._closeTimeout |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| function socketOnClose() { |
| const websocket = this[kWebSocket]; |
|
|
| this.removeListener('close', socketOnClose); |
| this.removeListener('data', socketOnData); |
| this.removeListener('end', socketOnEnd); |
|
|
| websocket._readyState = WebSocket.CLOSING; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if ( |
| !this._readableState.endEmitted && |
| !websocket._closeFrameReceived && |
| !websocket._receiver._writableState.errorEmitted && |
| this._readableState.length !== 0 |
| ) { |
| const chunk = this.read(this._readableState.length); |
|
|
| websocket._receiver.write(chunk); |
| } |
|
|
| websocket._receiver.end(); |
|
|
| this[kWebSocket] = undefined; |
|
|
| clearTimeout(websocket._closeTimer); |
|
|
| if ( |
| websocket._receiver._writableState.finished || |
| websocket._receiver._writableState.errorEmitted |
| ) { |
| websocket.emitClose(); |
| } else { |
| websocket._receiver.on('error', receiverOnFinish); |
| websocket._receiver.on('finish', receiverOnFinish); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function socketOnData(chunk) { |
| if (!this[kWebSocket]._receiver.write(chunk)) { |
| this.pause(); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function socketOnEnd() { |
| const websocket = this[kWebSocket]; |
|
|
| websocket._readyState = WebSocket.CLOSING; |
| websocket._receiver.end(); |
| this.end(); |
| } |
|
|
| |
| |
| |
| |
| |
| function socketOnError() { |
| const websocket = this[kWebSocket]; |
|
|
| this.removeListener('error', socketOnError); |
| this.on('error', NOOP); |
|
|
| if (websocket) { |
| websocket._readyState = WebSocket.CLOSING; |
| this.destroy(); |
| } |
| } |
|
|