termbridge 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1788,6 +1788,22 @@ var parseArgs = (argv) => {
1788
1788
  options.port = port;
1789
1789
  continue;
1790
1790
  }
1791
+ if (current === "--proxy") {
1792
+ const proxy = parseNumber(args.shift());
1793
+ if (!proxy || proxy <= 0) {
1794
+ throw new Error("invalid proxy port");
1795
+ }
1796
+ options.proxy = proxy;
1797
+ continue;
1798
+ }
1799
+ if (current === "--dev-proxy-url") {
1800
+ const url = args.shift();
1801
+ if (!url) {
1802
+ throw new Error("missing dev proxy URL");
1803
+ }
1804
+ options.devProxyUrl = url;
1805
+ continue;
1806
+ }
1791
1807
  if (current === "--session") {
1792
1808
  const session = args.shift();
1793
1809
  if (!session) {
@@ -1825,6 +1841,7 @@ Usage:
1825
1841
 
1826
1842
  Options:
1827
1843
  --port <port> Bind the local server to a fixed port
1844
+ --proxy <port> Proxy a local dev server (e.g., Vite) through termbridge
1828
1845
  --session <name> Use a specific tmux session name
1829
1846
  --kill-on-exit Kill the tmux session when the CLI exits
1830
1847
  --no-qr Disable QR code output
@@ -2246,9 +2263,9 @@ var createTerminalRegistry = () => {
2246
2263
  };
2247
2264
 
2248
2265
  // src/server/server.ts
2249
- import { createServer as createHttpServer } from "http";
2266
+ import { createServer as createHttpServer, request as httpRequest } from "http";
2250
2267
  import { randomBytes as randomBytes3 } from "crypto";
2251
- import { WebSocketServer } from "ws";
2268
+ import { WebSocketServer, WebSocket as WsWebSocket } from "ws";
2252
2269
 
2253
2270
  // ../packages/shared/src/index.ts
2254
2271
  var TERMINAL_CONTROL_KEYS = [
@@ -2385,24 +2402,44 @@ var sendWsMessage = (socket, message) => {
2385
2402
  };
2386
2403
  var createSessionName = () => `termbridge-${randomBytes3(4).toString("hex")}`;
2387
2404
  var createAppServer = (deps) => {
2388
- const staticHandler = createStaticHandler(deps.uiDistPath, "/app");
2405
+ const staticHandler = createStaticHandler(deps.uiDistPath, "/__tb/app");
2389
2406
  const wss = new WebSocketServer({ noServer: true });
2390
2407
  const connectionInfo = /* @__PURE__ */ new WeakMap();
2408
+ const proxyRequest = (request, response, targetPath, search) => {
2409
+ const targetUrl = `http://localhost:${deps.proxyPort}${targetPath}${search}`;
2410
+ const proxyHeaders = { ...request.headers };
2411
+ delete proxyHeaders.cookie;
2412
+ delete proxyHeaders.host;
2413
+ proxyHeaders.host = `localhost:${deps.proxyPort}`;
2414
+ const proxyReq = httpRequest(
2415
+ targetUrl,
2416
+ { method: request.method, headers: proxyHeaders },
2417
+ (proxyRes) => {
2418
+ response.writeHead(proxyRes.statusCode, proxyRes.headers);
2419
+ proxyRes.pipe(response);
2420
+ }
2421
+ );
2422
+ proxyReq.on("error", () => {
2423
+ response.statusCode = 502;
2424
+ response.end("proxy error");
2425
+ });
2426
+ request.pipe(proxyReq);
2427
+ };
2391
2428
  const server = createHttpServer(async (request, response) => {
2392
2429
  const url = new URL(request.url, `http://${request.headers.host}`);
2393
- if (request.method === "GET" && url.pathname === "/healthz") {
2430
+ if (request.method === "GET" && url.pathname === "/__tb/healthz") {
2394
2431
  response.statusCode = 200;
2395
2432
  response.end("ok");
2396
2433
  return;
2397
2434
  }
2398
- if (request.method === "GET" && url.pathname === "/") {
2435
+ if (request.method === "GET" && url.pathname === "/" && !deps.proxyPort) {
2399
2436
  response.statusCode = 302;
2400
- response.setHeader("Location", "/app");
2437
+ response.setHeader("Location", "/__tb/app");
2401
2438
  response.end();
2402
2439
  return;
2403
2440
  }
2404
- if (request.method === "GET" && url.pathname.startsWith("/s/")) {
2405
- const token = url.pathname.slice(3);
2441
+ if (request.method === "GET" && url.pathname.startsWith("/__tb/s/")) {
2442
+ const token = url.pathname.slice("/__tb/s/".length);
2406
2443
  const ip = getIp(request);
2407
2444
  if (!deps.redemptionLimiter.allow(ip)) {
2408
2445
  response.statusCode = 429;
@@ -2417,11 +2454,11 @@ var createAppServer = (deps) => {
2417
2454
  }
2418
2455
  response.statusCode = 302;
2419
2456
  response.setHeader("Set-Cookie", deps.auth.createSessionCookie(session.id));
2420
- response.setHeader("Location", "/app");
2457
+ response.setHeader("Location", "/__tb/app");
2421
2458
  response.end();
2422
2459
  return;
2423
2460
  }
2424
- if (url.pathname === "/api/csrf") {
2461
+ if (url.pathname === "/__tb/api/csrf") {
2425
2462
  const session = deps.auth.getSessionFromRequest(request);
2426
2463
  if (!session) {
2427
2464
  response.statusCode = 401;
@@ -2436,7 +2473,7 @@ var createAppServer = (deps) => {
2436
2473
  response.end("not found");
2437
2474
  return;
2438
2475
  }
2439
- if (url.pathname === "/api/terminals") {
2476
+ if (url.pathname === "/__tb/api/terminals") {
2440
2477
  const session = deps.auth.getSessionFromRequest(request);
2441
2478
  if (!session) {
2442
2479
  response.statusCode = 401;
@@ -2467,15 +2504,48 @@ var createAppServer = (deps) => {
2467
2504
  return;
2468
2505
  }
2469
2506
  }
2507
+ if (request.method === "GET" && url.pathname === "/__tb/api/config") {
2508
+ const session = deps.auth.getSessionFromRequest(request);
2509
+ if (!session) {
2510
+ response.statusCode = 401;
2511
+ response.end("unauthorized");
2512
+ return;
2513
+ }
2514
+ jsonResponse(response, 200, {
2515
+ proxyPort: deps.proxyPort ?? null,
2516
+ devProxyUrl: deps.devProxyUrl ?? null
2517
+ });
2518
+ return;
2519
+ }
2470
2520
  const handled = await staticHandler(request, response);
2471
2521
  if (!handled) {
2522
+ if (deps.proxyPort && deps.auth.getSessionFromRequest(request)) {
2523
+ proxyRequest(request, response, url.pathname, url.search);
2524
+ return;
2525
+ }
2472
2526
  response.statusCode = 404;
2473
2527
  response.end("not found");
2474
2528
  }
2475
2529
  });
2476
2530
  server.on("upgrade", (request, socket, head) => {
2477
2531
  const url = new URL(request.url, `http://${request.headers.host}`);
2478
- if (!url.pathname.startsWith("/ws/terminal/")) {
2532
+ if (!url.pathname.startsWith("/__tb/ws/terminal/")) {
2533
+ if (deps.proxyPort && deps.auth.getSessionFromRequest(request)) {
2534
+ const targetUrl = `ws://localhost:${deps.proxyPort}${url.pathname}${url.search}`;
2535
+ const proxyWs = new WsWebSocket(targetUrl);
2536
+ proxyWs.on("open", () => {
2537
+ wss.handleUpgrade(request, socket, head, (clientWs) => {
2538
+ clientWs.on("message", (data) => proxyWs.send(data));
2539
+ proxyWs.on("message", (data) => clientWs.send(data));
2540
+ clientWs.on("close", () => proxyWs.close());
2541
+ proxyWs.on("close", () => clientWs.close());
2542
+ });
2543
+ });
2544
+ proxyWs.on("error", () => {
2545
+ socket.destroy();
2546
+ });
2547
+ return;
2548
+ }
2479
2549
  socket.destroy();
2480
2550
  return;
2481
2551
  }
@@ -2615,7 +2685,9 @@ var startCommand = async (options, deps = {}) => {
2615
2685
  uiDistPath: resolveUiDistPath(),
2616
2686
  auth,
2617
2687
  terminalRegistry,
2618
- terminalBackend
2688
+ terminalBackend,
2689
+ proxyPort: options.proxy,
2690
+ devProxyUrl: options.devProxyUrl
2619
2691
  });
2620
2692
  const started = await server.listen(options.port ?? 0);
2621
2693
  const localUrl = `http://127.0.0.1:${started.port}`;
@@ -2640,7 +2712,7 @@ var startCommand = async (options, deps = {}) => {
2640
2712
  await started.close();
2641
2713
  throw error;
2642
2714
  }
2643
- const redeemUrl = `${publicUrl}/s/${token}`;
2715
+ const redeemUrl = `${publicUrl}/__tb/s/${token}`;
2644
2716
  logger.info(`Local server: ${localUrl}`);
2645
2717
  logger.info(`Tunnel URL: ${redeemUrl}`);
2646
2718
  if (!options.noQr && deps.qr) {