Spaces:
Runtime error
Runtime error
| const { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = require('./symbols') | |
| const { states, opcodes } = require('./constants') | |
| const { MessageEvent, ErrorEvent } = require('./events') | |
| /* globals Blob */ | |
| /** | |
| * @param {import('./websocket').WebSocket} ws | |
| */ | |
| function isEstablished (ws) { | |
| // If the server's response is validated as provided for above, it is | |
| // said that _The WebSocket Connection is Established_ and that the | |
| // WebSocket Connection is in the OPEN state. | |
| return ws[kReadyState] === states.OPEN | |
| } | |
| /** | |
| * @param {import('./websocket').WebSocket} ws | |
| */ | |
| function isClosing (ws) { | |
| // Upon either sending or receiving a Close control frame, it is said | |
| // that _The WebSocket Closing Handshake is Started_ and that the | |
| // WebSocket connection is in the CLOSING state. | |
| return ws[kReadyState] === states.CLOSING | |
| } | |
| /** | |
| * @param {import('./websocket').WebSocket} ws | |
| */ | |
| function isClosed (ws) { | |
| return ws[kReadyState] === states.CLOSED | |
| } | |
| /** | |
| * @see https://dom.spec.whatwg.org/#concept-event-fire | |
| * @param {string} e | |
| * @param {EventTarget} target | |
| * @param {EventInit | undefined} eventInitDict | |
| */ | |
| function fireEvent (e, target, eventConstructor = Event, eventInitDict) { | |
| // 1. If eventConstructor is not given, then let eventConstructor be Event. | |
| // 2. Let event be the result of creating an event given eventConstructor, | |
| // in the relevant realm of target. | |
| // 3. Initialize event’s type attribute to e. | |
| const event = new eventConstructor(e, eventInitDict) // eslint-disable-line new-cap | |
| // 4. Initialize any other IDL attributes of event as described in the | |
| // invocation of this algorithm. | |
| // 5. Return the result of dispatching event at target, with legacy target | |
| // override flag set if set. | |
| target.dispatchEvent(event) | |
| } | |
| /** | |
| * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol | |
| * @param {import('./websocket').WebSocket} ws | |
| * @param {number} type Opcode | |
| * @param {Buffer} data application data | |
| */ | |
| function websocketMessageReceived (ws, type, data) { | |
| // 1. If ready state is not OPEN (1), then return. | |
| if (ws[kReadyState] !== states.OPEN) { | |
| return | |
| } | |
| // 2. Let dataForEvent be determined by switching on type and binary type: | |
| let dataForEvent | |
| if (type === opcodes.TEXT) { | |
| // -> type indicates that the data is Text | |
| // a new DOMString containing data | |
| try { | |
| dataForEvent = new TextDecoder('utf-8', { fatal: true }).decode(data) | |
| } catch { | |
| failWebsocketConnection(ws, 'Received invalid UTF-8 in text frame.') | |
| return | |
| } | |
| } else if (type === opcodes.BINARY) { | |
| if (ws[kBinaryType] === 'blob') { | |
| // -> type indicates that the data is Binary and binary type is "blob" | |
| // a new Blob object, created in the relevant Realm of the WebSocket | |
| // object, that represents data as its raw data | |
| dataForEvent = new Blob([data]) | |
| } else { | |
| // -> type indicates that the data is Binary and binary type is "arraybuffer" | |
| // a new ArrayBuffer object, created in the relevant Realm of the | |
| // WebSocket object, whose contents are data | |
| dataForEvent = new Uint8Array(data).buffer | |
| } | |
| } | |
| // 3. Fire an event named message at the WebSocket object, using MessageEvent, | |
| // with the origin attribute initialized to the serialization of the WebSocket | |
| // object’s url's origin, and the data attribute initialized to dataForEvent. | |
| fireEvent('message', ws, MessageEvent, { | |
| origin: ws[kWebSocketURL].origin, | |
| data: dataForEvent | |
| }) | |
| } | |
| /** | |
| * @see https://datatracker.ietf.org/doc/html/rfc6455 | |
| * @see https://datatracker.ietf.org/doc/html/rfc2616 | |
| * @see https://bugs.chromium.org/p/chromium/issues/detail?id=398407 | |
| * @param {string} protocol | |
| */ | |
| function isValidSubprotocol (protocol) { | |
| // If present, this value indicates one | |
| // or more comma-separated subprotocol the client wishes to speak, | |
| // ordered by preference. The elements that comprise this value | |
| // MUST be non-empty strings with characters in the range U+0021 to | |
| // U+007E not including separator characters as defined in | |
| // [RFC2616] and MUST all be unique strings. | |
| if (protocol.length === 0) { | |
| return false | |
| } | |
| for (const char of protocol) { | |
| const code = char.charCodeAt(0) | |
| if ( | |
| code < 0x21 || | |
| code > 0x7E || | |
| char === '(' || | |
| char === ')' || | |
| char === '<' || | |
| char === '>' || | |
| char === '@' || | |
| char === ',' || | |
| char === ';' || | |
| char === ':' || | |
| char === '\\' || | |
| char === '"' || | |
| char === '/' || | |
| char === '[' || | |
| char === ']' || | |
| char === '?' || | |
| char === '=' || | |
| char === '{' || | |
| char === '}' || | |
| code === 32 || // SP | |
| code === 9 // HT | |
| ) { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| /** | |
| * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7-4 | |
| * @param {number} code | |
| */ | |
| function isValidStatusCode (code) { | |
| if (code >= 1000 && code < 1015) { | |
| return ( | |
| code !== 1004 && // reserved | |
| code !== 1005 && // "MUST NOT be set as a status code" | |
| code !== 1006 // "MUST NOT be set as a status code" | |
| ) | |
| } | |
| return code >= 3000 && code <= 4999 | |
| } | |
| /** | |
| * @param {import('./websocket').WebSocket} ws | |
| * @param {string|undefined} reason | |
| */ | |
| function failWebsocketConnection (ws, reason) { | |
| const { [kController]: controller, [kResponse]: response } = ws | |
| controller.abort() | |
| if (response?.socket && !response.socket.destroyed) { | |
| response.socket.destroy() | |
| } | |
| if (reason) { | |
| fireEvent('error', ws, ErrorEvent, { | |
| error: new Error(reason) | |
| }) | |
| } | |
| } | |
| module.exports = { | |
| isEstablished, | |
| isClosing, | |
| isClosed, | |
| fireEvent, | |
| isValidSubprotocol, | |
| isValidStatusCode, | |
| failWebsocketConnection, | |
| websocketMessageReceived | |
| } | |