termbridge 0.2.0 → 0.3.1
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 +94 -19
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
- package/ui/dist/assets/index-B6CAkCwJ.css +1 -0
- package/ui/dist/assets/index-CI7cgRhu.js +103 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-C9vi5hfL.css +0 -1
- package/ui/dist/assets/index-Cix4a2YI.js +0 -103
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
|
|
@@ -1866,7 +1883,8 @@ var defaultDeps = {
|
|
|
1866
1883
|
spawnPty: pty.spawn,
|
|
1867
1884
|
env: process.env,
|
|
1868
1885
|
defaultCols: 80,
|
|
1869
|
-
defaultRows: 24
|
|
1886
|
+
defaultRows: 24,
|
|
1887
|
+
_skipSpawnHelperCheck: false
|
|
1870
1888
|
};
|
|
1871
1889
|
var ensureSpawnHelperExecutable = () => {
|
|
1872
1890
|
if (spawnHelperChecked || process.platform === "win32") {
|
|
@@ -1939,7 +1957,9 @@ var createTmuxBackend = (deps = {}) => {
|
|
|
1939
1957
|
if (entry.pty) {
|
|
1940
1958
|
return entry.pty;
|
|
1941
1959
|
}
|
|
1942
|
-
|
|
1960
|
+
if (!runtime._skipSpawnHelperCheck) {
|
|
1961
|
+
ensureSpawnHelperExecutable();
|
|
1962
|
+
}
|
|
1943
1963
|
const ptyInstance = runtime.spawnPty("tmux", ["attach-session", "-t", entry.session.name], {
|
|
1944
1964
|
name: "xterm-256color",
|
|
1945
1965
|
cols: entry.cols,
|
|
@@ -2246,9 +2266,9 @@ var createTerminalRegistry = () => {
|
|
|
2246
2266
|
};
|
|
2247
2267
|
|
|
2248
2268
|
// src/server/server.ts
|
|
2249
|
-
import { createServer as createHttpServer } from "http";
|
|
2269
|
+
import { createServer as createHttpServer, request as httpRequest } from "http";
|
|
2250
2270
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
2251
|
-
import { WebSocketServer } from "ws";
|
|
2271
|
+
import { WebSocketServer, WebSocket as WsWebSocket } from "ws";
|
|
2252
2272
|
|
|
2253
2273
|
// ../packages/shared/src/index.ts
|
|
2254
2274
|
var TERMINAL_CONTROL_KEYS = [
|
|
@@ -2356,11 +2376,11 @@ var isAllowedOrigin = (origin, host) => {
|
|
|
2356
2376
|
};
|
|
2357
2377
|
var allowedControlKeys = new Set(TERMINAL_CONTROL_KEYS);
|
|
2358
2378
|
var parseClientMessage = (payload) => {
|
|
2359
|
-
const size = typeof payload === "string" ? payload.length : payload.byteLength;
|
|
2379
|
+
const size = typeof payload === "string" ? payload.length : Array.isArray(payload) ? payload.reduce((sum, buf) => sum + buf.length, 0) : payload.byteLength;
|
|
2360
2380
|
if (size > MAX_WS_MESSAGE_SIZE) {
|
|
2361
2381
|
return { ok: false, error: "too_large" };
|
|
2362
2382
|
}
|
|
2363
|
-
const text = typeof payload === "string" ? payload : payload.toString();
|
|
2383
|
+
const text = typeof payload === "string" ? payload : Array.isArray(payload) ? Buffer.concat(payload).toString() : payload.toString();
|
|
2364
2384
|
try {
|
|
2365
2385
|
const parsed = JSON.parse(text);
|
|
2366
2386
|
if (parsed.type === "input" && typeof parsed.data === "string") {
|
|
@@ -2385,24 +2405,44 @@ var sendWsMessage = (socket, message) => {
|
|
|
2385
2405
|
};
|
|
2386
2406
|
var createSessionName = () => `termbridge-${randomBytes3(4).toString("hex")}`;
|
|
2387
2407
|
var createAppServer = (deps) => {
|
|
2388
|
-
const staticHandler = createStaticHandler(deps.uiDistPath, "/app");
|
|
2408
|
+
const staticHandler = createStaticHandler(deps.uiDistPath, "/__tb/app");
|
|
2389
2409
|
const wss = new WebSocketServer({ noServer: true });
|
|
2390
2410
|
const connectionInfo = /* @__PURE__ */ new WeakMap();
|
|
2411
|
+
const proxyRequest = (request, response, targetPath, search) => {
|
|
2412
|
+
const targetUrl = `http://localhost:${deps.proxyPort}${targetPath}${search}`;
|
|
2413
|
+
const proxyHeaders = { ...request.headers };
|
|
2414
|
+
delete proxyHeaders.cookie;
|
|
2415
|
+
delete proxyHeaders.host;
|
|
2416
|
+
proxyHeaders.host = `localhost:${deps.proxyPort}`;
|
|
2417
|
+
const proxyReq = httpRequest(
|
|
2418
|
+
targetUrl,
|
|
2419
|
+
{ method: request.method, headers: proxyHeaders },
|
|
2420
|
+
(proxyRes) => {
|
|
2421
|
+
response.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
2422
|
+
proxyRes.pipe(response);
|
|
2423
|
+
}
|
|
2424
|
+
);
|
|
2425
|
+
proxyReq.on("error", () => {
|
|
2426
|
+
response.statusCode = 502;
|
|
2427
|
+
response.end("proxy error");
|
|
2428
|
+
});
|
|
2429
|
+
request.pipe(proxyReq);
|
|
2430
|
+
};
|
|
2391
2431
|
const server = createHttpServer(async (request, response) => {
|
|
2392
2432
|
const url = new URL(request.url, `http://${request.headers.host}`);
|
|
2393
|
-
if (request.method === "GET" && url.pathname === "/healthz") {
|
|
2433
|
+
if (request.method === "GET" && url.pathname === "/__tb/healthz") {
|
|
2394
2434
|
response.statusCode = 200;
|
|
2395
2435
|
response.end("ok");
|
|
2396
2436
|
return;
|
|
2397
2437
|
}
|
|
2398
|
-
if (request.method === "GET" && url.pathname === "/") {
|
|
2438
|
+
if (request.method === "GET" && url.pathname === "/" && !deps.proxyPort) {
|
|
2399
2439
|
response.statusCode = 302;
|
|
2400
|
-
response.setHeader("Location", "/app");
|
|
2440
|
+
response.setHeader("Location", "/__tb/app");
|
|
2401
2441
|
response.end();
|
|
2402
2442
|
return;
|
|
2403
2443
|
}
|
|
2404
|
-
if (request.method === "GET" && url.pathname.startsWith("/s/")) {
|
|
2405
|
-
const token = url.pathname.slice(
|
|
2444
|
+
if (request.method === "GET" && url.pathname.startsWith("/__tb/s/")) {
|
|
2445
|
+
const token = url.pathname.slice("/__tb/s/".length);
|
|
2406
2446
|
const ip = getIp(request);
|
|
2407
2447
|
if (!deps.redemptionLimiter.allow(ip)) {
|
|
2408
2448
|
response.statusCode = 429;
|
|
@@ -2417,11 +2457,11 @@ var createAppServer = (deps) => {
|
|
|
2417
2457
|
}
|
|
2418
2458
|
response.statusCode = 302;
|
|
2419
2459
|
response.setHeader("Set-Cookie", deps.auth.createSessionCookie(session.id));
|
|
2420
|
-
response.setHeader("Location", "/app");
|
|
2460
|
+
response.setHeader("Location", "/__tb/app");
|
|
2421
2461
|
response.end();
|
|
2422
2462
|
return;
|
|
2423
2463
|
}
|
|
2424
|
-
if (url.pathname === "/api/csrf") {
|
|
2464
|
+
if (url.pathname === "/__tb/api/csrf") {
|
|
2425
2465
|
const session = deps.auth.getSessionFromRequest(request);
|
|
2426
2466
|
if (!session) {
|
|
2427
2467
|
response.statusCode = 401;
|
|
@@ -2436,7 +2476,7 @@ var createAppServer = (deps) => {
|
|
|
2436
2476
|
response.end("not found");
|
|
2437
2477
|
return;
|
|
2438
2478
|
}
|
|
2439
|
-
if (url.pathname === "/api/terminals") {
|
|
2479
|
+
if (url.pathname === "/__tb/api/terminals") {
|
|
2440
2480
|
const session = deps.auth.getSessionFromRequest(request);
|
|
2441
2481
|
if (!session) {
|
|
2442
2482
|
response.statusCode = 401;
|
|
@@ -2467,15 +2507,48 @@ var createAppServer = (deps) => {
|
|
|
2467
2507
|
return;
|
|
2468
2508
|
}
|
|
2469
2509
|
}
|
|
2510
|
+
if (request.method === "GET" && url.pathname === "/__tb/api/config") {
|
|
2511
|
+
const session = deps.auth.getSessionFromRequest(request);
|
|
2512
|
+
if (!session) {
|
|
2513
|
+
response.statusCode = 401;
|
|
2514
|
+
response.end("unauthorized");
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
jsonResponse(response, 200, {
|
|
2518
|
+
proxyPort: deps.proxyPort ?? null,
|
|
2519
|
+
devProxyUrl: deps.devProxyUrl ?? null
|
|
2520
|
+
});
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2470
2523
|
const handled = await staticHandler(request, response);
|
|
2471
2524
|
if (!handled) {
|
|
2525
|
+
if (deps.proxyPort && deps.auth.getSessionFromRequest(request)) {
|
|
2526
|
+
proxyRequest(request, response, url.pathname, url.search);
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2472
2529
|
response.statusCode = 404;
|
|
2473
2530
|
response.end("not found");
|
|
2474
2531
|
}
|
|
2475
2532
|
});
|
|
2476
2533
|
server.on("upgrade", (request, socket, head) => {
|
|
2477
2534
|
const url = new URL(request.url, `http://${request.headers.host}`);
|
|
2478
|
-
if (!url.pathname.startsWith("/ws/terminal/")) {
|
|
2535
|
+
if (!url.pathname.startsWith("/__tb/ws/terminal/")) {
|
|
2536
|
+
if (deps.proxyPort && deps.auth.getSessionFromRequest(request)) {
|
|
2537
|
+
const targetUrl = `ws://localhost:${deps.proxyPort}${url.pathname}${url.search}`;
|
|
2538
|
+
const proxyWs = new WsWebSocket(targetUrl);
|
|
2539
|
+
proxyWs.on("open", () => {
|
|
2540
|
+
wss.handleUpgrade(request, socket, head, (clientWs) => {
|
|
2541
|
+
clientWs.on("message", (data) => proxyWs.send(data));
|
|
2542
|
+
proxyWs.on("message", (data) => clientWs.send(data));
|
|
2543
|
+
clientWs.on("close", () => proxyWs.close());
|
|
2544
|
+
proxyWs.on("close", () => clientWs.close());
|
|
2545
|
+
});
|
|
2546
|
+
});
|
|
2547
|
+
proxyWs.on("error", () => {
|
|
2548
|
+
socket.destroy();
|
|
2549
|
+
});
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2479
2552
|
socket.destroy();
|
|
2480
2553
|
return;
|
|
2481
2554
|
}
|
|
@@ -2615,7 +2688,9 @@ var startCommand = async (options, deps = {}) => {
|
|
|
2615
2688
|
uiDistPath: resolveUiDistPath(),
|
|
2616
2689
|
auth,
|
|
2617
2690
|
terminalRegistry,
|
|
2618
|
-
terminalBackend
|
|
2691
|
+
terminalBackend,
|
|
2692
|
+
proxyPort: options.proxy,
|
|
2693
|
+
devProxyUrl: options.devProxyUrl
|
|
2619
2694
|
});
|
|
2620
2695
|
const started = await server.listen(options.port ?? 0);
|
|
2621
2696
|
const localUrl = `http://127.0.0.1:${started.port}`;
|
|
@@ -2640,7 +2715,7 @@ var startCommand = async (options, deps = {}) => {
|
|
|
2640
2715
|
await started.close();
|
|
2641
2716
|
throw error;
|
|
2642
2717
|
}
|
|
2643
|
-
const redeemUrl = `${publicUrl}/s/${token}`;
|
|
2718
|
+
const redeemUrl = `${publicUrl}/__tb/s/${token}`;
|
|
2644
2719
|
logger.info(`Local server: ${localUrl}`);
|
|
2645
2720
|
logger.info(`Tunnel URL: ${redeemUrl}`);
|
|
2646
2721
|
if (!options.noQr && deps.qr) {
|
|
@@ -2724,7 +2799,7 @@ var buildSessionName = (options, localUrl) => {
|
|
|
2724
2799
|
return "termbridge";
|
|
2725
2800
|
}
|
|
2726
2801
|
};
|
|
2727
|
-
var buildRedeemUrl = (result) => `${result.publicUrl}/s/${result.token}`;
|
|
2802
|
+
var buildRedeemUrl = (result) => `${result.publicUrl}/__tb/s/${result.token}`;
|
|
2728
2803
|
var generateQr = async (text) => new Promise((resolve4) => {
|
|
2729
2804
|
qrcode.generate(text, { small: true }, (output) => resolve4(output));
|
|
2730
2805
|
});
|