Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
util.js323 linesDownload Raw Back to websocket
1'use strict'
2
3const { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = require('./symbols')
4const { states, opcodes } = require('./constants')
5const { ErrorEvent, createFastMessageEvent } = require('./events')
6const { isUtf8 } = require('node:buffer')
7const { collectASequenceOfCodePointsFast, removeHTTPWhitespace } = require('../fetch/data-url')
8
9/* globals Blob */
10
11/**
12 * @param {import('./websocket').WebSocket} ws
13 * @returns {boolean}
14 */
15function isConnecting (ws) {
16  // If the WebSocket connection is not yet established, and the connection
17  // is not yet closed, then the WebSocket connection is in the CONNECTING state.
18  return ws[kReadyState] === states.CONNECTING
19}
20
21/**
22 * @param {import('./websocket').WebSocket} ws
23 * @returns {boolean}
24 */
25function isEstablished (ws) {
26  // If the server's response is validated as provided for above, it is
27  // said that _The WebSocket Connection is Established_ and that the
28  // WebSocket Connection is in the OPEN state.
29  return ws[kReadyState] === states.OPEN
30}
31
32/**
33 * @param {import('./websocket').WebSocket} ws
34 * @returns {boolean}
35 */
36function isClosing (ws) {
37  // Upon either sending or receiving a Close control frame, it is said
38  // that _The WebSocket Closing Handshake is Started_ and that the
39  // WebSocket connection is in the CLOSING state.
40  return ws[kReadyState] === states.CLOSING
41}
42
43/**
44 * @param {import('./websocket').WebSocket} ws
45 * @returns {boolean}
46 */
47function isClosed (ws) {
48  return ws[kReadyState] === states.CLOSED
49}
50
51/**
52 * @see https://dom.spec.whatwg.org/#concept-event-fire
53 * @param {string} e
54 * @param {EventTarget} target
55 * @param {(...args: ConstructorParameters<typeof Event>) => Event} eventFactory
56 * @param {EventInit | undefined} eventInitDict
57 */
58function fireEvent (e, target, eventFactory = (type, init) => new Event(type, init), eventInitDict = {}) {
59  // 1. If eventConstructor is not given, then let eventConstructor be Event.
60
61  // 2. Let event be the result of creating an event given eventConstructor,
62  //    in the relevant realm of target.
63  // 3. Initialize event’s type attribute to e.
64  const event = eventFactory(e, eventInitDict)
65
66  // 4. Initialize any other IDL attributes of event as described in the
67  //    invocation of this algorithm.
68
69  // 5. Return the result of dispatching event at target, with legacy target
70  //    override flag set if set.
71  target.dispatchEvent(event)
72}
73
74/**
75 * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
76 * @param {import('./websocket').WebSocket} ws
77 * @param {number} type Opcode
78 * @param {Buffer} data application data
79 */
80function websocketMessageReceived (ws, type, data) {
81  // 1. If ready state is not OPEN (1), then return.
82  if (ws[kReadyState] !== states.OPEN) {
83    return
84  }
85
86  // 2. Let dataForEvent be determined by switching on type and binary type:
87  let dataForEvent
88
89  if (type === opcodes.TEXT) {
90    // -> type indicates that the data is Text
91    //      a new DOMString containing data
92    try {
93      dataForEvent = utf8Decode(data)
94    } catch {
95      failWebsocketConnection(ws, 'Received invalid UTF-8 in text frame.')
96      return
97    }
98  } else if (type === opcodes.BINARY) {
99    if (ws[kBinaryType] === 'blob') {
100      // -> type indicates that the data is Binary and binary type is "blob"
101      //      a new Blob object, created in the relevant Realm of the WebSocket
102      //      object, that represents data as its raw data
103      dataForEvent = new Blob([data])
104    } else {
105      // -> type indicates that the data is Binary and binary type is "arraybuffer"
106      //      a new ArrayBuffer object, created in the relevant Realm of the
107      //      WebSocket object, whose contents are data
108      dataForEvent = toArrayBuffer(data)
109    }
110  }
111
112  // 3. Fire an event named message at the WebSocket object, using MessageEvent,
113  //    with the origin attribute initialized to the serialization of the WebSocket
114  //    object’s url's origin, and the data attribute initialized to dataForEvent.
115  fireEvent('message', ws, createFastMessageEvent, {
116    origin: ws[kWebSocketURL].origin,
117    data: dataForEvent
118  })
119}
120
121function toArrayBuffer (buffer) {
122  if (buffer.byteLength === buffer.buffer.byteLength) {
123    return buffer.buffer
124  }
125  return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
126}
127
128/**
129 * @see https://datatracker.ietf.org/doc/html/rfc6455
130 * @see https://datatracker.ietf.org/doc/html/rfc2616
131 * @see https://bugs.chromium.org/p/chromium/issues/detail?id=398407
132 * @param {string} protocol
133 */
134function isValidSubprotocol (protocol) {
135  // If present, this value indicates one
136  // or more comma-separated subprotocol the client wishes to speak,
137  // ordered by preference.  The elements that comprise this value
138  // MUST be non-empty strings with characters in the range U+0021 to
139  // U+007E not including separator characters as defined in
140  // [RFC2616] and MUST all be unique strings.
141  if (protocol.length === 0) {
142    return false
143  }
144
145  for (let i = 0; i < protocol.length; ++i) {
146    const code = protocol.charCodeAt(i)
147
148    if (
149      code < 0x21 || // CTL, contains SP (0x20) and HT (0x09)
150      code > 0x7E ||
151      code === 0x22 || // "
152      code === 0x28 || // (
153      code === 0x29 || // )
154      code === 0x2C || // ,
155      code === 0x2F || // /
156      code === 0x3A || // :
157      code === 0x3B || // ;
158      code === 0x3C || // <
159      code === 0x3D || // =
160      code === 0x3E || // >
161      code === 0x3F || // ?
162      code === 0x40 || // @
163      code === 0x5B || // [
164      code === 0x5C || // \
165      code === 0x5D || // ]
166      code === 0x7B || // {
167      code === 0x7D // }
168    ) {
169      return false
170    }
171  }
172
173  return true
174}
175
176/**
177 * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7-4
178 * @param {number} code
179 */
180function isValidStatusCode (code) {
181  if (code >= 1000 && code < 1015) {
182    return (
183      code !== 1004 && // reserved
184      code !== 1005 && // "MUST NOT be set as a status code"
185      code !== 1006 // "MUST NOT be set as a status code"
186    )
187  }
188
189  return code >= 3000 && code <= 4999
190}
191
192/**
193 * @param {import('./websocket').WebSocket} ws
194 * @param {string|undefined} reason
195 */
196function failWebsocketConnection (ws, reason) {
197  const { [kController]: controller, [kResponse]: response } = ws
198
199  controller.abort()
200
201  if (response?.socket && !response.socket.destroyed) {
202    response.socket.destroy()
203  }
204
205  if (reason) {
206    // TODO: process.nextTick
207    fireEvent('error', ws, (type, init) => new ErrorEvent(type, init), {
208      error: new Error(reason),
209      message: reason
210    })
211  }
212}
213
214/**
215 * @see https://datatracker.ietf.org/doc/html/rfc6455#section-5.5
216 * @param {number} opcode
217 */
218function isControlFrame (opcode) {
219  return (
220    opcode === opcodes.CLOSE ||
221    opcode === opcodes.PING ||
222    opcode === opcodes.PONG
223  )
224}
225
226function isContinuationFrame (opcode) {
227  return opcode === opcodes.CONTINUATION
228}
229
230function isTextBinaryFrame (opcode) {
231  return opcode === opcodes.TEXT || opcode === opcodes.BINARY
232}
233
234function isValidOpcode (opcode) {
235  return isTextBinaryFrame(opcode) || isContinuationFrame(opcode) || isControlFrame(opcode)
236}
237
238/**
239 * Parses a Sec-WebSocket-Extensions header value.
240 * @param {string} extensions
241 * @returns {Map<string, string>}
242 */
243// TODO(@Uzlopak, @KhafraDev): make compliant https://datatracker.ietf.org/doc/html/rfc6455#section-9.1
244function parseExtensions (extensions) {
245  const position = { position: 0 }
246  const extensionList = new Map()
247
248  while (position.position < extensions.length) {
249    const pair = collectASequenceOfCodePointsFast(';', extensions, position)
250    const [name, value = ''] = pair.split('=')
251
252    extensionList.set(
253      removeHTTPWhitespace(name, true, false),
254      removeHTTPWhitespace(value, false, true)
255    )
256
257    position.position++
258  }
259
260  return extensionList
261}
262
263/**
264 * @see https://www.rfc-editor.org/rfc/rfc7692#section-7.1.2.2
265 * @description "client-max-window-bits = 1*DIGIT"
266 * @param {string} value
267 */
268function isValidClientWindowBits (value) {
269  // Must have at least one character
270  if (value.length === 0) {
271    return false
272  }
273
274  // Check all characters are ASCII digits
275  for (let i = 0; i < value.length; i++) {
276    const byte = value.charCodeAt(i)
277
278    if (byte < 0x30 || byte > 0x39) {
279      return false
280    }
281  }
282
283  // Check numeric range: zlib requires windowBits in range 8-15
284  const num = Number.parseInt(value, 10)
285  return num >= 8 && num <= 15
286}
287
288// https://nodejs.org/api/intl.html#detecting-internationalization-support
289const hasIntl = typeof process.versions.icu === 'string'
290const fatalDecoder = hasIntl ? new TextDecoder('utf-8', { fatal: true }) : undefined
291
292/**
293 * Converts a Buffer to utf-8, even on platforms without icu.
294 * @param {Buffer} buffer
295 */
296const utf8Decode = hasIntl
297  ? fatalDecoder.decode.bind(fatalDecoder)
298  : function (buffer) {
299    if (isUtf8(buffer)) {
300      return buffer.toString('utf-8')
301    }
302    throw new TypeError('Invalid utf-8 received.')
303  }
304
305module.exports = {
306  isConnecting,
307  isEstablished,
308  isClosing,
309  isClosed,
310  fireEvent,
311  isValidSubprotocol,
312  isValidStatusCode,
313  failWebsocketConnection,
314  websocketMessageReceived,
315  utf8Decode,
316  isControlFrame,
317  isContinuationFrame,
318  isTextBinaryFrame,
319  isValidOpcode,
320  parseExtensions,
321  isValidClientWindowBits
322}
323 
codekingpro/portable-devtools · Team Ai