File Explorer

/proc/self/root/var/runtime/node_modules/@aws-sdk/node_modules/undici/lib/web/websocket

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

websocket.js23.8 KB · 759 lines
'use strict' const { isArrayBuffer } = require('node:util/types')const { webidl } = require('../webidl')const { URLSerializer } = require('../fetch/data-url')const { environmentSettingsObject } = require('../fetch/util')const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes } = require('./constants')const {  isConnecting,  isEstablished,  isClosing,  isClosed,  isValidSubprotocol,  fireEvent,  utf8Decode,  toArrayBuffer,  getURLRecord} = require('./util')const { establishWebSocketConnection, closeWebSocketConnection, failWebsocketConnection } = require('./connection')const { ByteParser } = require('./receiver')const { kEnumerableProperty } = require('../../core/util')const { getGlobalDispatcher } = require('../../global')const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')const { SendQueue } = require('./sender')const { WebsocketFrameSend } = require('./frame')const { channels } = require('../../core/diagnostics') function getSocketAddress (socket) {  if (typeof socket?.address === 'function') {    return socket.address()  }   if (typeof socket?.session?.socket?.address === 'function') {    return socket.session.socket.address()  }   return null} /** * @typedef {object} Handler * @property {(response: any, extensions?: string[]) => void} onConnectionEstablished * @property {(opcode: number, data: Buffer) => void} onMessage * @property {(error: Error) => void} onParserError * @property {() => void} onParserDrain * @property {(chunk: Buffer) => void} onSocketData * @property {(err: Error) => void} onSocketError * @property {() => void} onSocketClose * @property {(body: Buffer) => void} onPing * @property {(body: Buffer) => void} onPong * * @property {number} readyState * @property {import('stream').Duplex} socket * @property {Set<number>} closeState * @property {import('../fetch/index').Fetch} controller * @property {boolean} [wasEverConnected=false] */ // https://websockets.spec.whatwg.org/#interface-definitionclass WebSocket extends EventTarget {  #events = {    open: null,    error: null,    close: null,    message: null  }   #bufferedAmount = 0  #protocol = ''  #extensions = ''   /** @type {SendQueue} */  #sendQueue   /** @type {Handler} */  #handler = {    onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),    onMessage: (opcode, data) => this.#onMessage(opcode, data),    onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),    onParserDrain: () => this.#onParserDrain(),    onSocketData: (chunk) => {      if (!this.#parser.write(chunk)) {        this.#handler.socket.pause()      }    },    onSocketError: (err) => {      this.#handler.readyState = states.CLOSING       if (channels.socketError.hasSubscribers) {        channels.socketError.publish(err)      }       this.#handler.socket.destroy()    },    onSocketClose: () => this.#onSocketClose(),    onPing: (body) => {      if (channels.ping.hasSubscribers) {        channels.ping.publish({          payload: body,          websocket: this        })      }    },    onPong: (body) => {      if (channels.pong.hasSubscribers) {        channels.pong.publish({          payload: body,          websocket: this        })      }    },     readyState: states.CONNECTING,    socket: null,    closeState: new Set(),    controller: null,    wasEverConnected: false  }   #url  #binaryType  /** @type {import('./receiver').ByteParser} */  #parser   /**   * @param {string} url   * @param {string|string[]} protocols   */  constructor (url, protocols = []) {    super()     webidl.util.markAsUncloneable(this)     const prefix = 'WebSocket constructor'    webidl.argumentLengthCheck(arguments, 1, prefix)     const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')     url = webidl.converters.USVString(url)    protocols = options.protocols     // 1. Let baseURL be this's relevant settings object's API base URL.    const baseURL = environmentSettingsObject.settingsObject.baseUrl     // 2. Let urlRecord be the result of getting a URL record given url and baseURL.    const urlRecord = getURLRecord(url, baseURL)     // 3. If protocols is a string, set protocols to a sequence consisting    //    of just that string.    if (typeof protocols === 'string') {      protocols = [protocols]    }     // 4. If any of the values in protocols occur more than once or otherwise    //    fail to match the requirements for elements that comprise the value    //    of `Sec-WebSocket-Protocol` fields as defined by The WebSocket    //    protocol, then throw a "SyntaxError" DOMException.    if (protocols.length !== new Set(protocols.map(p => p.toLowerCase())).size) {      throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')    }     if (protocols.length > 0 && !protocols.every(p => isValidSubprotocol(p))) {      throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')    }     // 5. Set this's url to urlRecord.    this.#url = new URL(urlRecord.href)     // 6. Let client be this's relevant settings object.    const client = environmentSettingsObject.settingsObject     // 7. Run this step in parallel:    // 7.1. Establish a WebSocket connection given urlRecord, protocols,    //      and client.    this.#handler.controller = establishWebSocketConnection(      urlRecord,      protocols,      client,      this.#handler,      options    )     // Each WebSocket object has an associated ready state, which is a    // number representing the state of the connection. Initially it must    // be CONNECTING (0).    this.#handler.readyState = WebSocket.CONNECTING     // The extensions attribute must initially return the empty string.     // The protocol attribute must initially return the empty string.     // Each WebSocket object has an associated binary type, which is a    // BinaryType. Initially it must be "blob".    this.#binaryType = 'blob'  }   /**   * @see https://websockets.spec.whatwg.org/#dom-websocket-close   * @param {number|undefined} code   * @param {string|undefined} reason   */  close (code = undefined, reason = undefined) {    webidl.brandCheck(this, WebSocket)     const prefix = 'WebSocket.close'     if (code !== undefined) {      code = webidl.converters['unsigned short'](code, prefix, 'code', webidl.attributes.Clamp)    }     if (reason !== undefined) {      reason = webidl.converters.USVString(reason)    }     // 1. If code is the special value "missing", then set code to null.    code ??= null     // 2. If reason is the special value "missing", then set reason to the empty string.    reason ??= ''     // 3. Close the WebSocket with this, code, and reason.    closeWebSocketConnection(this.#handler, code, reason, true)  }   /**   * @see https://websockets.spec.whatwg.org/#dom-websocket-send   * @param {NodeJS.TypedArray|ArrayBuffer|Blob|string} data   */  send (data) {    webidl.brandCheck(this, WebSocket)     const prefix = 'WebSocket.send'    webidl.argumentLengthCheck(arguments, 1, prefix)     data = webidl.converters.WebSocketSendData(data, prefix, 'data')     // 1. If this's ready state is CONNECTING, then throw an    //    "InvalidStateError" DOMException.    if (isConnecting(this.#handler.readyState)) {      throw new DOMException('Sent before connected.', 'InvalidStateError')    }     // 2. Run the appropriate set of steps from the following list:    // https://datatracker.ietf.org/doc/html/rfc6455#section-6.1    // https://datatracker.ietf.org/doc/html/rfc6455#section-5.2     if (!isEstablished(this.#handler.readyState) || isClosing(this.#handler.readyState)) {      return    }     // If data is a string    if (typeof data === 'string') {      // If the WebSocket connection is established and the WebSocket      // closing handshake has not yet started, then the user agent      // must send a WebSocket Message comprised of the data argument      // using a text frame opcode; if the data cannot be sent, e.g.      // because it would need to be buffered but the buffer is full,      // the user agent must flag the WebSocket as full and then close      // the WebSocket connection. Any invocation of this method with a      // string argument that does not throw an exception must increase      // the bufferedAmount attribute by the number of bytes needed to      // express the argument as UTF-8.       const buffer = Buffer.from(data)       this.#bufferedAmount += buffer.byteLength      this.#sendQueue.add(buffer, () => {        this.#bufferedAmount -= buffer.byteLength      }, sendHints.text)    } else if (isArrayBuffer(data)) {      // If the WebSocket connection is established, and the WebSocket      // closing handshake has not yet started, then the user agent must      // send a WebSocket Message comprised of data using a binary frame      // opcode; if the data cannot be sent, e.g. because it would need      // to be buffered but the buffer is full, the user agent must flag      // the WebSocket as full and then close the WebSocket connection.      // The data to be sent is the data stored in the buffer described      // by the ArrayBuffer object. Any invocation of this method with an      // ArrayBuffer argument that does not throw an exception must      // increase the bufferedAmount attribute by the length of the      // ArrayBuffer in bytes.       this.#bufferedAmount += data.byteLength      this.#sendQueue.add(data, () => {        this.#bufferedAmount -= data.byteLength      }, sendHints.arrayBuffer)    } else if (ArrayBuffer.isView(data)) {      // If the WebSocket connection is established, and the WebSocket      // closing handshake has not yet started, then the user agent must      // send a WebSocket Message comprised of data using a binary frame      // opcode; if the data cannot be sent, e.g. because it would need to      // be buffered but the buffer is full, the user agent must flag the      // WebSocket as full and then close the WebSocket connection. The      // data to be sent is the data stored in the section of the buffer      // described by the ArrayBuffer object that data references. Any      // invocation of this method with this kind of argument that does      // not throw an exception must increase the bufferedAmount attribute      // by the length of data’s buffer in bytes.       this.#bufferedAmount += data.byteLength      this.#sendQueue.add(data, () => {        this.#bufferedAmount -= data.byteLength      }, sendHints.typedArray)    } else if (webidl.is.Blob(data)) {      // If the WebSocket connection is established, and the WebSocket      // closing handshake has not yet started, then the user agent must      // send a WebSocket Message comprised of data using a binary frame      // opcode; if the data cannot be sent, e.g. because it would need to      // be buffered but the buffer is full, the user agent must flag the      // WebSocket as full and then close the WebSocket connection. The data      // to be sent is the raw data represented by the Blob object. Any      // invocation of this method with a Blob argument that does not throw      // an exception must increase the bufferedAmount attribute by the size      // of the Blob object’s raw data, in bytes.       this.#bufferedAmount += data.size      this.#sendQueue.add(data, () => {        this.#bufferedAmount -= data.size      }, sendHints.blob)    }  }   get readyState () {    webidl.brandCheck(this, WebSocket)     // The readyState getter steps are to return this's ready state.    return this.#handler.readyState  }   get bufferedAmount () {    webidl.brandCheck(this, WebSocket)     return this.#bufferedAmount  }   get url () {    webidl.brandCheck(this, WebSocket)     // The url getter steps are to return this's url, serialized.    return URLSerializer(this.#url)  }   get extensions () {    webidl.brandCheck(this, WebSocket)     return this.#extensions  }   get protocol () {    webidl.brandCheck(this, WebSocket)     return this.#protocol  }   get onopen () {    webidl.brandCheck(this, WebSocket)     return this.#events.open  }   set onopen (fn) {    webidl.brandCheck(this, WebSocket)     if (this.#events.open) {      this.removeEventListener('open', this.#events.open)    }     const listener = webidl.converters.EventHandlerNonNull(fn)     if (listener !== null) {      this.addEventListener('open', listener)      this.#events.open = fn    } else {      this.#events.open = null    }  }   get onerror () {    webidl.brandCheck(this, WebSocket)     return this.#events.error  }   set onerror (fn) {    webidl.brandCheck(this, WebSocket)     if (this.#events.error) {      this.removeEventListener('error', this.#events.error)    }     const listener = webidl.converters.EventHandlerNonNull(fn)     if (listener !== null) {      this.addEventListener('error', listener)      this.#events.error = fn    } else {      this.#events.error = null    }  }   get onclose () {    webidl.brandCheck(this, WebSocket)     return this.#events.close  }   set onclose (fn) {    webidl.brandCheck(this, WebSocket)     if (this.#events.close) {      this.removeEventListener('close', this.#events.close)    }     const listener = webidl.converters.EventHandlerNonNull(fn)     if (listener !== null) {      this.addEventListener('close', listener)      this.#events.close = fn    } else {      this.#events.close = null    }  }   get onmessage () {    webidl.brandCheck(this, WebSocket)     return this.#events.message  }   set onmessage (fn) {    webidl.brandCheck(this, WebSocket)     if (this.#events.message) {      this.removeEventListener('message', this.#events.message)    }     const listener = webidl.converters.EventHandlerNonNull(fn)     if (listener !== null) {      this.addEventListener('message', listener)      this.#events.message = fn    } else {      this.#events.message = null    }  }   get binaryType () {    webidl.brandCheck(this, WebSocket)     return this.#binaryType  }   set binaryType (type) {    webidl.brandCheck(this, WebSocket)     if (type !== 'blob' && type !== 'arraybuffer') {      this.#binaryType = 'blob'    } else {      this.#binaryType = type    }  }   /**   * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol   */  #onConnectionEstablished (response, parsedExtensions) {    // processResponse is called when the "response's header list has been received and initialized."    // once this happens, the connection is open    this.#handler.socket = response.socket     const webSocketOptions = this.#handler.controller.dispatcher?.webSocketOptions    const maxFragments = webSocketOptions?.maxFragments    const maxPayloadSize = webSocketOptions?.maxPayloadSize     const parser = new ByteParser(this.#handler, parsedExtensions, {      maxFragments,      maxPayloadSize    })    parser.on('drain', () => this.#handler.onParserDrain())    parser.on('error', (err) => this.#handler.onParserError(err))     this.#parser = parser    this.#sendQueue = new SendQueue(response.socket)     // 1. Change the ready state to OPEN (1).    this.#handler.readyState = states.OPEN     // 2. Change the extensions attribute’s value to the extensions in use, if    //    it is not the null value.    // https://datatracker.ietf.org/doc/html/rfc6455#section-9.1    const extensions = response.headersList.get('sec-websocket-extensions')     if (extensions !== null) {      this.#extensions = extensions    }     // 3. Change the protocol attribute’s value to the subprotocol in use, if    //    it is not the null value.    // https://datatracker.ietf.org/doc/html/rfc6455#section-1.9    const protocol = response.headersList.get('sec-websocket-protocol')     if (protocol !== null) {      this.#protocol = protocol    }     // 4. Fire an event named open at the WebSocket object.    fireEvent('open', this)     if (channels.open.hasSubscribers) {      // Convert headers to a plain object for the event      const headers = response.headersList.entries      channels.open.publish({        address: getSocketAddress(response.socket),        protocol: this.#protocol,        extensions: this.#extensions,        websocket: this,        handshakeResponse: {          status: response.status,          statusText: response.statusText,          headers        }      })    }  }   #onMessage (type, data) {    // 1. If ready state is not OPEN (1), then return.    if (this.#handler.readyState !== 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 = utf8Decode(data)      } catch {        failWebsocketConnection(this.#handler, 1007, 'Received invalid UTF-8 in text frame.')        return      }    } else if (type === opcodes.BINARY) {      if (this.#binaryType === '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 = toArrayBuffer(data)      }    }     // 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', this, createFastMessageEvent, {      origin: this.#url.origin,      data: dataForEvent    })  }   #onParserDrain () {    this.#handler.socket.resume()  }   /**   * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol   * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4   */  #onSocketClose () {    // If the TCP connection was closed after the    // WebSocket closing handshake was completed, the WebSocket connection    // is said to have been closed _cleanly_.    const wasClean =      this.#handler.closeState.has(sentCloseFrameState.SENT) &&      this.#handler.closeState.has(sentCloseFrameState.RECEIVED)     let code = 1005    let reason = ''     const result = this.#parser?.closingInfo     if (result && !result.error) {      code = result.code ?? 1005      reason = result.reason    }     // 1. Change the ready state to CLOSED (3).    this.#handler.readyState = states.CLOSED     // 2. If the user agent was required to fail the WebSocket    //    connection, or if the WebSocket connection was closed    //    after being flagged as full, fire an event named error    //    at the WebSocket object.    if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {      // If _The WebSocket      // Connection is Closed_ and no Close control frame was received by the      // endpoint (such as could occur if the underlying transport connection      // is lost), _The WebSocket Connection Close Code_ is considered to be      // 1006.      code = 1006       fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {        error: new TypeError(reason)      })    }     // 3. Fire an event named close at the WebSocket object,    //    using CloseEvent, with the wasClean attribute    //    initialized to true if the connection closed cleanly    //    and false otherwise, the code attribute initialized to    //    the WebSocket connection close code, and the reason    //    attribute initialized to the result of applying UTF-8    //    decode without BOM to the WebSocket connection close    //    reason.    // TODO: process.nextTick    fireEvent('close', this, (type, init) => new CloseEvent(type, init), {      wasClean, code, reason    })     if (channels.close.hasSubscribers) {      channels.close.publish({        websocket: this,        code,        reason      })    }  }   /**   * @param {WebSocket} ws   * @param {Buffer|undefined} buffer   */  static ping (ws, buffer) {    if (Buffer.isBuffer(buffer)) {      if (buffer.length > 125) {        throw new TypeError('A PING frame cannot have a body larger than 125 bytes.')      }    } else if (buffer !== undefined) {      throw new TypeError('Expected buffer payload')    }     // An endpoint MAY send a Ping frame any time after the connection is    // established and before the connection is closed.    const readyState = ws.#handler.readyState     if (isEstablished(readyState) && !isClosing(readyState) && !isClosed(readyState)) {      const frame = new WebsocketFrameSend(buffer)      ws.#handler.socket.write(frame.createFrame(opcodes.PING))    }  }} const { ping } = WebSocketReflect.deleteProperty(WebSocket, 'ping') // https://websockets.spec.whatwg.org/#dom-websocket-connectingWebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING// https://websockets.spec.whatwg.org/#dom-websocket-openWebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN// https://websockets.spec.whatwg.org/#dom-websocket-closingWebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING// https://websockets.spec.whatwg.org/#dom-websocket-closedWebSocket.CLOSED = WebSocket.prototype.CLOSED = states.CLOSED Object.defineProperties(WebSocket.prototype, {  CONNECTING: staticPropertyDescriptors,  OPEN: staticPropertyDescriptors,  CLOSING: staticPropertyDescriptors,  CLOSED: staticPropertyDescriptors,  url: kEnumerableProperty,  readyState: kEnumerableProperty,  bufferedAmount: kEnumerableProperty,  onopen: kEnumerableProperty,  onerror: kEnumerableProperty,  onclose: kEnumerableProperty,  close: kEnumerableProperty,  onmessage: kEnumerableProperty,  binaryType: kEnumerableProperty,  send: kEnumerableProperty,  extensions: kEnumerableProperty,  protocol: kEnumerableProperty,  [Symbol.toStringTag]: {    value: 'WebSocket',    writable: false,    enumerable: false,    configurable: true  }}) Object.defineProperties(WebSocket, {  CONNECTING: staticPropertyDescriptors,  OPEN: staticPropertyDescriptors,  CLOSING: staticPropertyDescriptors,  CLOSED: staticPropertyDescriptors}) webidl.converters['sequence<DOMString>'] = webidl.sequenceConverter(  webidl.converters.DOMString) webidl.converters['DOMString or sequence<DOMString>'] = function (V, prefix, argument) {  if (webidl.util.Type(V) === webidl.util.Types.OBJECT && Symbol.iterator in V) {    return webidl.converters['sequence<DOMString>'](V)  }   return webidl.converters.DOMString(V, prefix, argument)} // This implements the proposal made in https://github.com/whatwg/websockets/issues/42webidl.converters.WebSocketInit = webidl.dictionaryConverter([  {    key: 'protocols',    converter: webidl.converters['DOMString or sequence<DOMString>'],    defaultValue: () => []  },  {    key: 'dispatcher',    converter: webidl.converters.any,    defaultValue: () => getGlobalDispatcher()  },  {    key: 'headers',    converter: webidl.nullableConverter(webidl.converters.HeadersInit)  }]) webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) {  if (webidl.util.Type(V) === webidl.util.Types.OBJECT && !(Symbol.iterator in V)) {    return webidl.converters.WebSocketInit(V)  }   return { protocols: webidl.converters['DOMString or sequence<DOMString>'](V) }} webidl.converters.WebSocketSendData = function (V) {  if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {    if (webidl.is.Blob(V)) {      return V    }     if (webidl.is.BufferSource(V)) {      return V    }  }   return webidl.converters.USVString(V)} module.exports = {  WebSocket,  ping}