{"version":3,"sources":["../../src/mock-server.ts"],"sourcesContent":["import fs from \"fs/promises\";\nimport path from \"path\";\nimport {\n  http,\n  HttpResponse,\n  passthrough,\n  type DefaultBodyType,\n  type StrictRequest,\n} from \"msw\";\nimport { setupServer } from \"msw/node\";\nimport { type RawData } from \"ws\";\nimport mime from \"mime-types\";\nimport { clientIdentityStorage } from \"./trace.js\";\nimport { logger } from \"./logger.js\";\nimport {\n  connections,\n  type WebSocketConnectionState,\n} from \"./connection-state.js\";\nimport {\n  Message,\n  MessageType,\n  parseMessage,\n  type MessageTypes,\n  type ParsedMessageType,\n} from \"@mocky-balboa/websocket-messages\";\nimport { UnsetClientIdentity } from \"@mocky-balboa/shared-config\";\n\ninterface OnResponseFromClientParams {\n  requestId: string;\n  message: ParsedMessageType<MessageTypes[\"RESPONSE\"]>;\n}\n\nconst getContentType = (filePath: string) => {\n  const contentType = mime.contentType(path.basename(filePath));\n  return contentType || \"application/octet-stream\";\n};\n\nconst getFileContents = async (filePath: string) => {\n  const fileStats = await fs.stat(filePath);\n  if (!fileStats.isFile()) {\n    throw new Error(`Path ${filePath} is not a file`);\n  }\n\n  return fs.readFile(filePath);\n};\n\n/**\n * Converts a response object from the client to a Mock Service Worker HttpResponse.\n */\nconst convertResponseFromClientToHttpResponse = async ({\n  requestId,\n  message,\n}: OnResponseFromClientParams) => {\n  // Not concerning our request\n  if (message.payload.id !== requestId) {\n    return;\n  }\n\n  // If the client has specified a network error, send the response as an error\n  if (message.payload.error) return HttpResponse.error();\n\n  // If there's no response from the client, pass the request through to the network\n  if (!message.payload.response) return passthrough();\n\n  // If there's a path from the client, load the file content and send it as the response\n  if (message.payload.response.path) {\n    const content = await getFileContents(message.payload.response.path);\n    const headers = new Headers(message.payload.response.headers);\n    // Prioritize the content type set on the headers sent from the client\n    const contentType =\n      headers.get(\"content-type\") ??\n      getContentType(message.payload.response.path);\n\n    headers.set(\"content-type\", contentType);\n    return new HttpResponse(content, {\n      status: message.payload.response.status,\n      headers,\n    });\n  }\n\n  return new HttpResponse(message.payload.response.body, {\n    status: message.payload.response.status,\n    headers: message.payload.response.headers,\n  });\n};\n\n/**\n * Processes the request to retrieve a response from the client falling back to passing the request through to the target URL.\n *\n * @param connectionState - the WebSocket connection state containing the WebSocket connection\n * @param requestId - unique identifier for the request\n * @param request - the request object\n * @param timeoutDuration - the duration in milliseconds to wait for a response from the client\n * @returns A mock service worker HTTP response\n */\nconst getResponseFromClient = async (\n  connectionState: WebSocketConnectionState,\n  requestId: string,\n  request: StrictRequest<DefaultBodyType>,\n  timeoutDuration: number,\n): Promise<HttpResponse<string>> => {\n  const requestBody = await new Response(request.body).text();\n  return new Promise<HttpResponse<string>>((resolve, reject) => {\n    const timeout = setTimeout(() => {\n      connectionState.ws.off(\"message\", onMessage);\n      reject(new Error(\"Request timed out\"));\n    }, timeoutDuration);\n\n    async function onMessage(data: RawData) {\n      try {\n        const message = parseMessage(data.toString());\n\n        switch (message.type) {\n          case MessageType.RESPONSE:\n            try {\n              const httpResponse =\n                await convertResponseFromClientToHttpResponse({\n                  requestId,\n                  message,\n                });\n\n              if (!httpResponse) {\n                return;\n              }\n\n              clearTimeout(timeout);\n              connectionState.ws.off(\"message\", onMessage);\n\n              connectionState.ws.send(\n                new Message(MessageType.ACK, {}, message.messageId).toString(),\n              );\n\n              resolve(httpResponse);\n            } catch (error) {\n              reject(error);\n            }\n            break;\n        }\n      } catch (error) {\n        logger.error(\"Error occurred while processing message\", error, {\n          clientIdentity: connectionState.clientIdentity,\n          requestId,\n        });\n      }\n    }\n\n    connectionState.ws.on(\"message\", onMessage);\n\n    const message = new Message(MessageType.REQUEST, {\n      id: requestId,\n      request: {\n        url: request.url,\n        method: request.method,\n        headers: Object.fromEntries(request.headers.entries()),\n        body: requestBody,\n      },\n    });\n\n    connectionState.ws.send(message.toString());\n  });\n};\n\nexport interface MockServerOptions {\n  /**\n   * Time out for the mock server to receive a response from the client\n   *\n   * @default 5000\n   */\n  timeout?: number;\n}\n\n/**\n * Sets up mock service worker to handle all requests via the WebSocket client connections\n */\nexport const bindMockServiceWorker = ({\n  timeout = 5000,\n}: MockServerOptions = {}) => {\n  const server = setupServer(\n    http.all(\"*\", async (req) => {\n      const clientIdentity = clientIdentityStorage.getStore();\n      const requestLogContext = {\n        url: req.request.url,\n        headers: Object.fromEntries(req.request.headers),\n      };\n      if (!clientIdentity || clientIdentity === UnsetClientIdentity) {\n        logger.info(\"Client not identified\", requestLogContext);\n        return passthrough();\n      }\n\n      const connectionState = connections.get(clientIdentity);\n      if (!connectionState) {\n        logger.warn(\"Client connection not found\", {\n          ...requestLogContext,\n          clientIdentity,\n        });\n        return passthrough();\n      }\n\n      try {\n        const response = await getResponseFromClient(\n          connectionState,\n          req.requestId,\n          req.request,\n          timeout,\n        );\n        return response;\n      } catch (error) {\n        logger.error(\n          \"Error occurred while attempting to resolve response\",\n          error,\n        );\n\n        const errorMessage = new Message(MessageType.ERROR, {\n          id: req.requestId,\n          message: error instanceof Error ? error.message : \"Unknown error\",\n        });\n\n        connectionState.ws.send(errorMessage.toString());\n\n        return HttpResponse.error();\n      }\n    }),\n  );\n\n  server.listen({ onUnhandledRequest: \"bypass\" });\n};\n"],"names":["bindMockServiceWorker","getContentType","filePath","contentType","mime","path","basename","getFileContents","fileStats","fs","stat","isFile","Error","readFile","convertResponseFromClientToHttpResponse","requestId","message","payload","id","error","HttpResponse","response","passthrough","content","headers","Headers","get","set","status","body","getResponseFromClient","connectionState","request","timeoutDuration","requestBody","Response","text","Promise","resolve","reject","timeout","setTimeout","ws","off","onMessage","data","parseMessage","toString","type","MessageType","RESPONSE","httpResponse","clearTimeout","send","Message","ACK","messageId","logger","clientIdentity","on","REQUEST","url","method","Object","fromEntries","entries","server","setupServer","http","all","req","clientIdentityStorage","getStore","requestLogContext","UnsetClientIdentity","info","connections","warn","errorMessage","ERROR","listen","onUnhandledRequest"],"mappings":"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"}