Files
server/opus.ts
T

111 lines
3.0 KiB
TypeScript

import { serve, type Socket } from "bun";
import { uid, parseResponse, wait } from "./utils";
const clientSockets = new Map<string, Socket>();
const clientData = new Map<string, any>();
const port = Bun.env.PORT || 1234;
const protocol = Bun.env.PROTOCOL || "http";
serve({
port,
fetch: async (req: Request) => {
const url = new URL(req.url);
const hostname = `${url.hostname}${url.port && `:${url.port}`}`;
// For healthchecks
if (url.pathname === "/healthz") return new Response("healthy");
if (url.searchParams.has("new")) {
const clientId = uid();
const server = Bun.listen({
hostname: "localhost",
port: 0, // Let the OS assign an available port
socket: {
data(_socket, data) {
clientData.set(clientId, data);
},
open(socket) {
console.log(`Client ${clientId} connected`);
clientSockets.set(clientId, socket);
},
close() {
console.log(`Client ${clientId} reconnecting`);
clientSockets.delete(clientId);
clientData.delete(clientId);
},
error(_socket, error) {
console.error(`Error for client ${clientId}:`, error);
},
},
});
return new Response(
JSON.stringify({
id: clientId,
port: server.port,
url: `${protocol}://${clientId}.${hostname}`,
}),
{
headers: { "Content-Type": "application/json" },
status: 200,
}
);
}
if (hostname.split(".").length === 3) {
return new Response("welcome to tunnel. hit ?new to get a URL.");
}
const subdomain = hostname.split(".")[0];
const clientSocket = clientSockets.get(subdomain);
if (!clientSocket) {
console.warn(`Client ${subdomain} not found`);
return new Response("Client not found", { status: 404 });
}
console.log(`Forwarding request to client ${subdomain}`);
// Forward the request to the connected client
console.log(req.method, url.pathname, url.search);
clientSocket.write(
`${req.method} ${url.pathname}${url.search} HTTP/1.1\r\n`
);
for (const [header, value] of req.headers.entries()) {
clientSocket.write(`${header}: ${value}\r\n`);
}
clientSocket.write("\r\n");
if (req.body) {
const reader = req.body.getReader();
let chunk;
while (!(chunk = await reader.read()).done) {
clientSocket.write(chunk.value);
}
}
let responseData = clientData.get(subdomain);
let attempts = 10;
while (!responseData) {
await wait(100);
responseData = clientData.get(subdomain);
if (attempts < 1) {
return new Response("Client did not respond", { status: 504 });
}
}
const { body, headers } = parseResponse(responseData);
return new Response(body, { headers, status: 200 });
},
error(error) {
console.error(error);
return new Response(error.message, { status: 500 });
},
});
console.log(`Listening on port ${port}`);