File size: 10,355 Bytes
9ada4bc
1
{"version":3,"sources":["../../src/RemoteHttpInterceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA8BO,IAAM,wBAAN,cAAoC,iBAEzC;AAAA,EACA,cAAc;AACZ,UAAM;AAAA,MACJ,MAAM;AAAA,MACN,cAAc;AAAA,QACZ,IAAI,yBAAyB;AAAA,QAC7B,IAAI,0BAA0B;AAAA,MAChC;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEU,QAAQ;AAChB,UAAM,MAAM;AAEZ,QAAI;AAEJ,SAAK,GAAG,WAAW,OAAO,EAAE,SAAS,UAAU,MAAM;AAhDzD;AAmDM,YAAM,oBAAoB,KAAK,UAAU;AAAA,QACvC,IAAI;AAAA,QACJ,QAAQ,QAAQ;AAAA,QAChB,KAAK,QAAQ;AAAA,QACb,SAAS,MAAM,KAAK,QAAQ,QAAQ,QAAQ,CAAC;AAAA,QAC7C,aAAa,QAAQ;AAAA,QACrB,MAAM,CAAC,OAAO,MAAM,EAAE,SAAS,QAAQ,MAAM,IACzC,OACA,MAAM,QAAQ,KAAK;AAAA,MACzB,CAAsB;AAEtB,WAAK,OAAO;AAAA,QACV;AAAA,QACA;AAAA,MACF;AACA,oBAAQ,SAAR,iCAAe,WAAW;AAE1B,YAAM,kBAAkB,IAAI,QAAc,CAAC,YAAY;AACrD,8BAAsB,CAAC,YAAY;AACjC,cAAI,OAAO,YAAY,UAAU;AAC/B,mBAAO,QAAQ;AAAA,UACjB;AAEA,cAAI,QAAQ,WAAW,YAAY,WAAW,GAAG;AAC/C,kBAAM,CAAC,EAAE,kBAAkB,IACzB,QAAQ,MAAM,qBAAqB,KAAK,CAAC;AAE3C,gBAAI,CAAC,oBAAoB;AACvB,qBAAO,QAAQ;AAAA,YACjB;AAEA,kBAAM,eAAe,KAAK;AAAA,cACxB;AAAA,YACF;AAEA,kBAAM,iBAAiB,IAAI,SAAS,aAAa,MAAM;AAAA,cACrD,QAAQ,aAAa;AAAA,cACrB,YAAY,aAAa;AAAA,cACzB,SAAS,aAAa;AAAA,YACxB,CAAC;AAED,oBAAQ,YAAY,cAAc;AAClC,mBAAO,QAAQ;AAAA,UACjB;AAAA,QACF;AAAA,MACF,CAAC;AAGD,WAAK,OAAO;AAAA,QACV;AAAA,QACA;AAAA,MACF;AACA,cAAQ,YAAY,WAAW,mBAAmB;AAElD,aAAO;AAAA,IACT,CAAC;AAED,SAAK,cAAc,KAAK,MAAM;AAC5B,cAAQ,eAAe,WAAW,mBAAmB;AAAA,IACvD,CAAC;AAAA,EACH;AACF;AAEO,SAAS,eAAe,KAAa,OAAY;AACtD,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,aAAO,IAAI,IAAI,KAAK;AAAA,IAEtB,KAAK;AACH,aAAO,IAAI,QAAQ,KAAK;AAAA,IAE1B;AACE,aAAO;AAAA,EACX;AACF;AAMO,IAAM,sBAAN,cAAiC,YAAiC;AAAA,EAIvE,YAAY,SAAgC;AAC1C,UAAM,oBAAmB,MAAM;AAC/B,SAAK,UAAU,QAAQ;AAAA,EACzB;AAAA,EAEU,QAAQ;AAChB,UAAM,SAAS,KAAK,OAAO,OAAO,OAAO;AAEzC,UAAM,qBAA6C,OAAO,YAAY;AACpE,aAAO,KAAK,gCAAgC,OAAO;AAEnD,UAAI,OAAO,YAAY,YAAY,CAAC,QAAQ,WAAW,UAAU,GAAG;AAClE,eAAO,KAAK,8BAA8B;AAC1C;AAAA,MACF;AAEA,YAAM,CAAC,EAAE,iBAAiB,IAAI,QAAQ,MAAM,gBAAgB,KAAK,CAAC;AAClE,UAAI,CAAC,mBAAmB;AACtB;AAAA,MACF;AAEA,YAAM,cAAc,KAAK;AAAA,QACvB;AAAA,QACA;AAAA,MACF;AACA,aAAO,KAAK,8BAA8B,WAAW;AAErD,YAAM,kBAAkB,IAAI,QAAQ,YAAY,KAAK;AAAA,QACnD,QAAQ,YAAY;AAAA,QACpB,SAAS,IAAI,QAAQ,YAAY,OAAO;AAAA,QACxC,aAAa,YAAY;AAAA,QACzB,MAAM,YAAY;AAAA,MACpB,CAAC;AAED,YAAM,EAAE,oBAAoB,kBAAkB,IAC5C,qBAAqB,eAAe;AAEtC,WAAK,QAAQ,KAAK,WAAW,MAAM;AACjC,YAAI,kBAAkB,gBAAgB,UAAU,WAAW;AACzD,4BAAkB,YAAY,MAAS;AAAA,QACzC;AAAA,MACF,CAAC;AAED,YAAM,UAAU,KAAK,SAAS,WAAW;AAAA,QACvC,SAAS;AAAA,QACT,WAAW,YAAY;AAAA,MACzB,CAAC;AAED,YAAM,iBAAiB,MAAM,kBAAkB;AAE/C,UAAI,CAAC,gBAAgB;AACnB;AAAA,MACF;AAEA,aAAO,KAAK,kCAAkC,cAAc;AAC5D,YAAM,gBAAgB,eAAe,MAAM;AAC3C,YAAM,eAAe,MAAM,eAAe,KAAK;AAG/C,YAAM,qBAAqB,KAAK,UAAU;AAAA,QACxC,QAAQ,eAAe;AAAA,QACvB,YAAY,eAAe;AAAA,QAC3B,SAAS,MAAM,KAAK,eAAe,QAAQ,QAAQ,CAAC;AAAA,QACpD,MAAM;AAAA,MACR,CAAuB;AAEvB,WAAK,QAAQ;AAAA,QACX,YAAY,YAAY,MAAM;AAAA,QAC9B,CAAC,UAAU;AACT,cAAI,OAAO;AACT;AAAA,UACF;AAIA,eAAK,QAAQ,KAAK,YAAY;AAAA,YAC5B,UAAU;AAAA,YACV,kBAAkB;AAAA,YAClB,SAAS;AAAA,YACT,WAAW,YAAY;AAAA,UACzB,CAAC;AAAA,QACH;AAAA,MACF;AAEA,aAAO;AAAA,QACL;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,SAAK,cAAc,KAAK,MAAM;AAC5B,WAAK,QAAQ,eAAe,WAAW,kBAAkB;AACzD,aAAO,KAAK,wDAAwD;AAAA,IACtE,CAAC;AAED,WAAO,KAAK,kDAAkD;AAC9D,SAAK,QAAQ,YAAY,WAAW,kBAAkB;AAEtD,SAAK,QAAQ,KAAK,SAAS,MAAM,KAAK,QAAQ,CAAC;AAC/C,SAAK,QAAQ,KAAK,QAAQ,MAAM,KAAK,QAAQ,CAAC;AAAA,EAChD;AACF;AAzGO,IAAM,qBAAN;AAAM,mBACJ,SAAS,OAAO,iBAAiB","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 { toInteractiveRequest } from './utils/toInteractiveRequest'\nimport { emitAsync } from './utils/emitAsync'\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]\n> {\n  constructor() {\n    super({\n      name: 'remote-interceptor',\n      interceptors: [\n        new ClientRequestInterceptor(),\n        new XMLHttpRequestInterceptor(),\n      ],\n    })\n  }\n\n  protected setup() {\n    super.setup()\n\n    let handleParentMessage: NodeJS.MessageListener\n\n    this.on('request', async ({ request, requestId }) => {\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      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 Response(responseInit.body, {\n              status: responseInit.status,\n              statusText: responseInit.statusText,\n              headers: responseInit.headers,\n            })\n\n            request.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      logger.info('parsed intercepted request', requestJson)\n\n      const capturedRequest = 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 { interactiveRequest, requestController } =\n        toInteractiveRequest(capturedRequest)\n\n      this.emitter.once('request', () => {\n        if (requestController.responsePromise.state === 'pending') {\n          requestController.respondWith(undefined)\n        }\n      })\n\n      await emitAsync(this.emitter, 'request', {\n        request: interactiveRequest,\n        requestId: requestJson.id,\n      })\n\n      const mockedResponse = await requestController.responsePromise\n\n      if (!mockedResponse) {\n        return\n      }\n\n      logger.info('event.respondWith called with:', mockedResponse)\n      const responseClone = mockedResponse.clone()\n      const responseText = await mockedResponse.text()\n\n      // Send the mocked response to the child process.\n      const serializedResponse = JSON.stringify({\n        status: mockedResponse.status,\n        statusText: mockedResponse.statusText,\n        headers: Array.from(mockedResponse.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            response: responseClone,\n            isMockedResponse: true,\n            request: capturedRequest,\n            requestId: requestJson.id,\n          })\n        }\n      )\n\n      logger.info(\n        'sent serialized mocked response to the parent:',\n        serializedResponse\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"]}