{"version":3,"sources":["../../src/RemoteHttpInterceptor.ts"],"sourcesContent":["import { ChildProcess } from 'child_process'\nimport { HttpRequestEventMap } from './glossary'\nimport { Interceptor } from './Interceptor'\nimport { BatchInterceptor } from './BatchInterceptor'\nimport { ClientRequestInterceptor } from './interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from './interceptors/XMLHttpRequest'\nimport { FetchInterceptor } from './interceptors/fetch'\nimport { handleRequest } from './utils/handleRequest'\nimport { RequestController } from './RequestController'\nimport { FetchResponse } from './utils/fetchUtils'\n\nexport interface SerializedRequest {\n  id: string\n  url: string\n  method: string\n  headers: Array<[string, string]>\n  credentials: RequestCredentials\n  body: string\n}\n\ninterface RevivedRequest extends Omit<SerializedRequest, 'url' | 'headers'> {\n  url: URL\n  headers: Headers\n}\n\nexport interface SerializedResponse {\n  status: number\n  statusText: string\n  headers: Array<[string, string]>\n  body: string\n}\n\nexport class RemoteHttpInterceptor extends BatchInterceptor<\n  [ClientRequestInterceptor, XMLHttpRequestInterceptor, FetchInterceptor]\n> {\n  constructor() {\n    super({\n      name: 'remote-interceptor',\n      interceptors: [\n        new ClientRequestInterceptor(),\n        new XMLHttpRequestInterceptor(),\n        new FetchInterceptor(),\n      ],\n    })\n  }\n\n  protected setup() {\n    super.setup()\n\n    let handleParentMessage: NodeJS.MessageListener\n\n    this.on('request', async ({ request, requestId, controller }) => {\n      // Send the stringified intercepted request to\n      // the parent process where the remote resolver is established.\n      const serializedRequest = JSON.stringify({\n        id: requestId,\n        method: request.method,\n        url: request.url,\n        headers: Array.from(request.headers.entries()),\n        credentials: request.credentials,\n        body: ['GET', 'HEAD'].includes(request.method)\n          ? null\n          : await request.text(),\n      } as SerializedRequest)\n\n      this.logger.info(\n        'sent serialized request to the child:',\n        serializedRequest\n      )\n\n      process.send?.(`request:${serializedRequest}`)\n\n      const responsePromise = new Promise<void>((resolve) => {\n        handleParentMessage = (message) => {\n          if (typeof message !== 'string') {\n            return resolve()\n          }\n\n          if (message.startsWith(`response:${requestId}`)) {\n            const [, serializedResponse] =\n              message.match(/^response:.+?:(.+)$/) || []\n\n            if (!serializedResponse) {\n              return resolve()\n            }\n\n            const responseInit = JSON.parse(\n              serializedResponse\n            ) as SerializedResponse\n\n            const mockedResponse = new FetchResponse(responseInit.body, {\n              url: request.url,\n              status: responseInit.status,\n              statusText: responseInit.statusText,\n              headers: responseInit.headers,\n            })\n\n            /**\n             * @todo Support \"errorWith\" as well.\n             * This response handling from the child is incomplete.\n             */\n\n            controller.respondWith(mockedResponse)\n            return resolve()\n          }\n        }\n      })\n\n      // Listen for the mocked response message from the parent.\n      this.logger.info(\n        'add \"message\" listener to the parent process',\n        handleParentMessage\n      )\n      process.addListener('message', handleParentMessage)\n\n      return responsePromise\n    })\n\n    this.subscriptions.push(() => {\n      process.removeListener('message', handleParentMessage)\n    })\n  }\n}\n\nexport function requestReviver(key: string, value: any) {\n  switch (key) {\n    case 'url':\n      return new URL(value)\n\n    case 'headers':\n      return new Headers(value)\n\n    default:\n      return value\n  }\n}\n\nexport interface RemoveResolverOptions {\n  process: ChildProcess\n}\n\nexport class RemoteHttpResolver extends Interceptor<HttpRequestEventMap> {\n  static symbol = Symbol('remote-resolver')\n  private process: ChildProcess\n\n  constructor(options: RemoveResolverOptions) {\n    super(RemoteHttpResolver.symbol)\n    this.process = options.process\n  }\n\n  protected setup() {\n    const logger = this.logger.extend('setup')\n\n    const handleChildMessage: NodeJS.MessageListener = async (message) => {\n      logger.info('received message from child!', message)\n\n      if (typeof message !== 'string' || !message.startsWith('request:')) {\n        logger.info('unknown message, ignoring...')\n        return\n      }\n\n      const [, serializedRequest] = message.match(/^request:(.+)$/) || []\n      if (!serializedRequest) {\n        return\n      }\n\n      const requestJson = JSON.parse(\n        serializedRequest,\n        requestReviver\n      ) as RevivedRequest\n\n      logger.info('parsed intercepted request', requestJson)\n\n      const request = new Request(requestJson.url, {\n        method: requestJson.method,\n        headers: new Headers(requestJson.headers),\n        credentials: requestJson.credentials,\n        body: requestJson.body,\n      })\n\n      const controller = new RequestController(request)\n      await handleRequest({\n        request,\n        requestId: requestJson.id,\n        controller,\n        emitter: this.emitter,\n        onResponse: async (response) => {\n          this.logger.info('received mocked response!', { response })\n\n          const responseClone = response.clone()\n          const responseText = await responseClone.text()\n\n          // // Send the mocked response to the child process.\n          const serializedResponse = JSON.stringify({\n            status: response.status,\n            statusText: response.statusText,\n            headers: Array.from(response.headers.entries()),\n            body: responseText,\n          } as SerializedResponse)\n\n          this.process.send(\n            `response:${requestJson.id}:${serializedResponse}`,\n            (error) => {\n              if (error) {\n                return\n              }\n\n              // Emit an optimistic \"response\" event at this point,\n              // not to rely on the back-and-forth signaling for the sake of the event.\n              this.emitter.emit('response', {\n                request,\n                requestId: requestJson.id,\n                response: responseClone,\n                isMockedResponse: true,\n              })\n            }\n          )\n\n          logger.info(\n            'sent serialized mocked response to the parent:',\n            serializedResponse\n          )\n        },\n        onRequestError: (response) => {\n          this.logger.info('received a network error!', { response })\n          throw new Error('Not implemented')\n        },\n        onError: (error) => {\n          this.logger.info('request has errored!', { error })\n          throw new Error('Not implemented')\n        },\n      })\n    }\n\n    this.subscriptions.push(() => {\n      this.process.removeListener('message', handleChildMessage)\n      logger.info('removed the \"message\" listener from the child process!')\n    })\n\n    logger.info('adding a \"message\" listener to the child process')\n    this.process.addListener('message', handleChildMessage)\n\n    this.process.once('error', () => this.dispose())\n    this.process.once('exit', () => this.dispose())\n  }\n}\n"],"mappings":"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","names":[]}