{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport { WebSocket } from \"partysocket\";\n\nexport const RequestMesssage = z.object({\n\ttype: z.literal(\"request\"),\n\tid: z.string(),\n\theaders: z.record(z.string()),\n\tmethod: z.string(),\n\turl: z.string(),\n\tbody: z.string(),\n\thttps: z.boolean().default(false).optional(),\n});\n\nexport type RequestMessage = z.infer<typeof RequestMesssage>;\n\nexport const ResponseMessage = z.object({\n\ttype: z.literal(\"response\"),\n\tid: z.string(),\n\tstatus: z.number(),\n\theaders: z.record(z.string()),\n\tbody: z.string(),\n});\n\nexport type ResponseMessage = z.infer<typeof ResponseMessage>;\n\nexport const ClientMessages = z.discriminatedUnion(\"type\", [ResponseMessage]);\nexport const ServerMessages = z.discriminatedUnion(\"type\", [RequestMesssage]);\n\nexport type ClientMessage = z.infer<typeof ClientMessages>;\nexport type ServerMessage = z.infer<typeof ServerMessages>;\n\nexport type RequestInfo = any;\nexport type RequestInit = any;\n\nexport async function createRequestMessage(id: string, request: Request): Promise<RequestMessage> {\n\tconst { headers, method, url } = request;\n\tconst body = await request.text();\n\n\treturn {\n\t\ttype: \"request\",\n\t\tid,\n\t\theaders: stripHeaders(Object.fromEntries(headers)),\n\t\tmethod,\n\t\turl,\n\t\tbody: method === 'GET' ? '' : body\n\t};\n}\n\nasync function createResponseMessage(id: string, response: Response): Promise<ResponseMessage> {\n\tconst { headers, status } = response;\n\n\tconst body = await response.text();\n\n\treturn {\n\t\ttype: \"response\",\n\t\tid,\n\t\theaders: Object.fromEntries(headers),\n\t\tstatus,\n\t\tbody,\n\t};\n}\n\nexport class NtfrApiClient {\n\tconstructor(\n\t\tprivate host: string,\n\t\tprivate apiKey: string\n\t) {}\n\n\tasync createTunnel(): Promise<string> {\n\t\tconst response = await fetch(`https://admin.${this.host}/api/tunnels`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\taccept: \"application/json\",\n\t\t\t\tauthorization: `Bearer ${this.apiKey}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Could not create tunnel: ${response.status}`);\n\t\t}\n\n\t\tconst body = (await response.json()) as any;\n\n\t\treturn body.id;\n\t}\n\n}\n\nexport type NtfrTunnelSocketOptions = {\n\tWebSocket?: any;\n\tconnectionTimeout?: number;\n\tmaxRetries?: number;\n};\n\nexport type NtfrTunnelOptions = {\n\tverbose?: boolean;\n};\n\ntype StatusResponse = boolean | undefined;\n\nexport class NtfrTunnel {\n\tsocket?: WebSocket;\n\n\tconstructor(\n\t\tprivate url: string,\n\t\tprivate address: string,\n\t\tprivate https: boolean,\n\t\tprivate socketOptions: NtfrTunnelSocketOptions = {},\n\t\tprivate options: NtfrTunnelOptions = {}\n\t) {}\n\n\tprivate log(message: string, properties: Record<string, any> = {}) {\n\t\tif (this.options.verbose) {\n\t\t\tconsole.log(JSON.stringify({ message, ...properties }));\n\t\t}\n\t}\n\n\tget isConnected(): StatusResponse {\n\t\tif(this.socket?.readyState === undefined) return undefined;\n\n\t\treturn this.socket?.readyState === this.socket?.OPEN;\n\t}\n\n\tget isConnecting(): StatusResponse {\n\t\tif(this.socket?.readyState === undefined) return undefined;\n\n\t\treturn this.socket?.readyState === this.socket?.CONNECTING;\n\t}\n\n\tget isClosed(): StatusResponse {\n\t\tif(this.socket?.readyState === undefined) return undefined;\n\n\t\treturn this.socket?.readyState === this.socket?.CLOSED;\n\t}\n\n\tget isClosing() : StatusResponse{\n\t\tif(this.socket?.readyState === undefined) return undefined;\n\n\t\treturn this.socket?.readyState === this.socket?.CLOSING;\n\t}\n\n\tasync connect() {\n\t\tthis.log(\"Connecting to tunnel\", {\n\t\t\turl: this.url,\n\t\t\taddress: this.address,\n\t\t\tsocketOptions: this.socketOptions,\n\t\t});\n\n\t\tthis.socket = new WebSocket(`wss://${this.url}/connect`, [], this.socketOptions);\n\n\t\tif(!this.socket) throw new Error(\"Socket is not connected\");\n\n\t\tthis.socket.addEventListener(\"close\", (event) => {\n\t\t\tthis.log(\"Disconnected from tunnel\", { event: event.code, reason: event.reason });\n\t\t});\n\n\t\tthis.socket.addEventListener(\"message\", async (event) => {\n\t\t\tconst data = JSON.parse(\n\t\t\t\ttypeof event.data === \"string\" ? event.data : new TextDecoder(\"utf-8\").decode(event.data)\n\t\t\t);\n\n\t\t\tconst message = ServerMessages.safeParse(data);\n\n\t\t\tif (!message.success) {\n\t\t\t\tthis.log(\"Received invalid message\", { data });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tswitch (message.data.type) {\n\t\t\t\tcase \"request\": {\n\t\t\t\t\tawait this.handleRequest(message.data);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\tconsole.error(`Unknown message type: ${message.data.type}`);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\n\t\t\tif(!this.socket) throw new Error(\"Socket is not connected\");\n\n\t\t\tthis.socket.addEventListener(\"open\", (args) => {\n\t\t\t\tthis.log(\"Connected to tunnel\");\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\tthis.socket.addEventListener(\"error\", (event) => {\n\t\t\t\tthis.log(\"Socket error\", { error: event.message });\n\t\t\t\treject()\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate async handleRequest(request: RequestMessage) {\n\t\tif (!this.socket) {\n\t\t\tthrow new Error(\"Socket is not connected\");\n\t\t}\n\n\t\tconst url = new URL(request.url);\n\t\t// Construct the original url to be the same as the request URL but with a different hostname and using http instead of https\n\t\tconst originalUrl = new URL(\n\t\t\t`${this.https ? \"https\" : \"http\"}://${this.address}${url.pathname}${url.search}${url.hash}`\n\t\t);\n\n\t\tlet response: Response | null = null;\n\n\t\tthis.log(\"Sending local request\", {\n\t\t\toriginalUrl: originalUrl.href,\n\t\t\trequestId: request.id,\n\t\t\theaders: request.headers,\n\t\t\tmethod: request.method\n\t\t});\n\n\t\ttry {\n\t\t\tresponse = await fetch(originalUrl.href, {\n\t\t\t\tmethod: request.method,\n\t\t\t\theaders: stripHeaders(request.headers),\n\t\t\t\tbody: request.method === 'GET' ? undefined : request.body,\n\t\t\t}) as any;\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error) {\n\t\t\t\tthis.log(\"Error sending local request\", {\n\t\t\t\t\terror: error.message,\n\t\t\t\t\tname: error.name,\n\t\t\t\t\tstack: error.stack,\n\t\t\t\t\trequestId: request.id,\n\t\t\t\t\tcause: \"cause\" in error ? error.cause : undefined,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.log(\"Error sending local request\", { error, requestId: request.id });\n\t\t\t}\n\n\t\t\t// Return a 502 response\n\t\t\tresponse = new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tmessage: `Could not connect to ${originalUrl.href}. Make sure you are running your local app server`,\n\t\t\t\t}),\n\t\t\t\t{ status: 502, headers: { \"Content-Type\": \"application/json\" } }\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.sendResponse(request.id, response as any, this.socket);\n\t\t} catch (error) {\n\t\t\tconsole.error(error);\n\t\t}\n\t}\n\n\tprivate async sendResponse(id: string, response: Response, socket: WebSocket) {\n\t\tconst message = await createResponseMessage(id, response);\n\n\t\tthis.log(\"Sending response\", { requestId: id, status: response.status, data: JSON.stringify(message) });\n\n\t\treturn socket.send(JSON.stringify(message));\n\t}\n}\n\n// Remove headers that should not be included like connection, host, etc\nfunction stripHeaders(headers: Record<string, string>) {\n\tconst blacklistHeaders = [\n\t\t\"connection\",\n\t\t\"cf-ray\",\n\t\t\"cf-connecting-ip\",\n\t\t\"host\",\n\t\t\"cf-ipcountry\",\n\t\t\"content-length\",\n\t];\n\n\treturn Object.fromEntries(\n\t\tObject.entries(headers).filter(([key]) => !blacklistHeaders.includes(key.toLowerCase()))\n\t);\n}\n"]}