{"version":3,"sources":["../../../src/core/handlers/WebSocketHandler.ts"],"sourcesContent":["import { Emitter } from 'strict-event-emitter'\nimport { createRequestId, resolveWebSocketUrl } from '@mswjs/interceptors'\nimport type {\n  WebSocketClientConnectionProtocol,\n  WebSocketConnectionData,\n  WebSocketServerConnectionProtocol,\n} from '@mswjs/interceptors/WebSocket'\nimport {\n  type Match,\n  type Path,\n  type PathParams,\n  matchRequestUrl,\n} from '../utils/matching/matchRequestUrl'\nimport { getCallFrame } from '../utils/internal/getCallFrame'\nimport type { HandlerKind } from './common'\n\ntype WebSocketHandlerParsedResult = {\n  match: Match\n}\n\nexport type WebSocketHandlerEventMap = {\n  connection: [args: WebSocketHandlerConnection]\n}\n\nexport interface WebSocketHandlerConnection {\n  client: WebSocketClientConnectionProtocol\n  server: WebSocketServerConnectionProtocol\n  info: WebSocketConnectionData['info']\n  params: PathParams\n}\n\nexport interface WebSocketResolutionContext {\n  baseUrl?: string\n}\n\nexport const kEmitter = Symbol('kEmitter')\nexport const kSender = Symbol('kSender')\nconst kStopPropagationPatched = Symbol('kStopPropagationPatched')\nconst KOnStopPropagation = Symbol('KOnStopPropagation')\n\nexport class WebSocketHandler {\n  private readonly __kind: HandlerKind\n\n  public id: string\n  public callFrame?: string\n\n  protected [kEmitter]: Emitter<WebSocketHandlerEventMap>\n\n  constructor(protected readonly url: Path) {\n    this.id = createRequestId()\n\n    this[kEmitter] = new Emitter()\n    this.callFrame = getCallFrame(new Error())\n    this.__kind = 'EventHandler'\n  }\n\n  public parse(args: {\n    url: URL\n    resolutionContext?: WebSocketResolutionContext\n  }): WebSocketHandlerParsedResult {\n    const clientUrl = new URL(args.url)\n\n    // Resolve the WebSocket handler path:\n    // - Plain string URLs resolved as per the specification (via Interceptors).\n    // - String URLs starting with a wildcard are preserved (prepending a scheme there will break them).\n    // - RegExp paths are preserved.\n    const resolvedHandlerUrl =\n      this.url instanceof RegExp || this.url.startsWith('*')\n        ? this.url\n        : this.#resolveWebSocketUrl(this.url, args.resolutionContext?.baseUrl)\n\n    /**\n     * @note Remove the Socket.IO path prefix from the WebSocket\n     * client URL. This is an exception to keep the users from\n     * including the implementation details in their handlers.\n     */\n    clientUrl.pathname = clientUrl.pathname.replace(/^\\/socket.io\\//, '/')\n\n    const match = matchRequestUrl(\n      clientUrl,\n      resolvedHandlerUrl,\n      args.resolutionContext?.baseUrl,\n    )\n\n    return {\n      match,\n    }\n  }\n\n  public predicate(args: {\n    url: URL\n    parsedResult: WebSocketHandlerParsedResult\n  }): boolean {\n    return args.parsedResult.match.matches\n  }\n\n  public async run(\n    connection: Omit<WebSocketHandlerConnection, 'params'>,\n    resolutionContext?: WebSocketResolutionContext,\n  ): Promise<boolean> {\n    const parsedResult = this.parse({\n      url: connection.client.url,\n      resolutionContext,\n    })\n\n    if (!this.predicate({ url: connection.client.url, parsedResult })) {\n      return false\n    }\n\n    const resolvedConnection: WebSocketHandlerConnection = {\n      ...connection,\n      params: parsedResult.match.params || {},\n    }\n\n    return this.connect(resolvedConnection)\n  }\n\n  protected connect(connection: WebSocketHandlerConnection): boolean {\n    // Support `event.stopPropagation()` for various client/server events.\n    connection.client.addEventListener(\n      'message',\n      createStopPropagationListener(this),\n    )\n    connection.client.addEventListener(\n      'close',\n      createStopPropagationListener(this),\n    )\n\n    connection.server.addEventListener(\n      'open',\n      createStopPropagationListener(this),\n    )\n    connection.server.addEventListener(\n      'message',\n      createStopPropagationListener(this),\n    )\n    connection.server.addEventListener(\n      'error',\n      createStopPropagationListener(this),\n    )\n    connection.server.addEventListener(\n      'close',\n      createStopPropagationListener(this),\n    )\n\n    // Emit the connection event on the handler.\n    // This is what the developer adds listeners for.\n    return this[kEmitter].emit('connection', connection)\n  }\n\n  #resolveWebSocketUrl(url: string, baseUrl?: string): string {\n    const resolvedUrl = resolveWebSocketUrl(\n      baseUrl\n        ? /**\n           * @note Resolve against the base URL preemtively because `resolveWebSocketUrl` only\n           * resolves against `location.href`, which is missing in Node.js. Base URL allows\n           * the handler to accept a relative URL in Node.js.\n           */\n          new URL(url, baseUrl)\n        : url,\n    )\n\n    /**\n     * @note Omit the trailing slash.\n     * While the browser always produces a trailing slash at the end of a WebSocket URL,\n     * having it in as the handler's predicate would mean it is *required* in the actual URL.\n     */\n    return resolvedUrl.replace(/\\/$/, '')\n  }\n}\n\nfunction createStopPropagationListener(handler: WebSocketHandler) {\n  return function stopPropagationListener(event: Event) {\n    const propagationStoppedAt = Reflect.get(event, 'kPropagationStoppedAt') as\n      | string\n      | undefined\n\n    if (propagationStoppedAt && handler.id !== propagationStoppedAt) {\n      event.stopImmediatePropagation()\n      return\n    }\n\n    Object.defineProperty(event, KOnStopPropagation, {\n      value(this: WebSocketHandler) {\n        Object.defineProperty(event, 'kPropagationStoppedAt', {\n          value: handler.id,\n        })\n      },\n      configurable: true,\n    })\n\n    // Since the same event instance is shared between all client/server objects,\n    // make sure to patch its `stopPropagation` method only once.\n    if (!Reflect.get(event, kStopPropagationPatched)) {\n      event.stopPropagation = new Proxy(event.stopPropagation, {\n        apply: (target, thisArg, args) => {\n          Reflect.get(event, KOnStopPropagation)?.call(handler)\n          return Reflect.apply(target, thisArg, args)\n        },\n      })\n\n      Object.defineProperty(event, kStopPropagationPatched, {\n        value: true,\n        // If something else attempts to redefine this, throw.\n        configurable: false,\n      })\n    }\n  }\n}\n"],"mappings":"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","names":[]}