termbridge 0.3.0 → 0.3.2
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
|
@@ -1828,6 +1828,22 @@ var parseArgs = (argv) => {
|
|
|
1828
1828
|
options.tunnel = "cloudflare";
|
|
1829
1829
|
continue;
|
|
1830
1830
|
}
|
|
1831
|
+
if (current === "--tunnel-token") {
|
|
1832
|
+
const token = args.shift();
|
|
1833
|
+
if (!token) {
|
|
1834
|
+
throw new Error("missing tunnel token");
|
|
1835
|
+
}
|
|
1836
|
+
options.tunnelToken = token;
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
if (current === "--tunnel-url") {
|
|
1840
|
+
const url = args.shift();
|
|
1841
|
+
if (!url) {
|
|
1842
|
+
throw new Error("missing tunnel url");
|
|
1843
|
+
}
|
|
1844
|
+
options.tunnelUrl = url;
|
|
1845
|
+
continue;
|
|
1846
|
+
}
|
|
1831
1847
|
throw new Error(`unknown option: ${current}`);
|
|
1832
1848
|
}
|
|
1833
1849
|
return { command, options };
|
|
@@ -1842,6 +1858,8 @@ Usage:
|
|
|
1842
1858
|
Options:
|
|
1843
1859
|
--port <port> Bind the local server to a fixed port
|
|
1844
1860
|
--proxy <port> Proxy a local dev server (e.g., Vite) through termbridge
|
|
1861
|
+
--tunnel-token <t> Use a named Cloudflare Tunnel token (requires --port)
|
|
1862
|
+
--tunnel-url <url> Public URL for a named tunnel (required with --tunnel-token)
|
|
1845
1863
|
--session <name> Use a specific tmux session name
|
|
1846
1864
|
--kill-on-exit Kill the tmux session when the CLI exits
|
|
1847
1865
|
--no-qr Disable QR code output
|
|
@@ -1883,7 +1901,8 @@ var defaultDeps = {
|
|
|
1883
1901
|
spawnPty: pty.spawn,
|
|
1884
1902
|
env: process.env,
|
|
1885
1903
|
defaultCols: 80,
|
|
1886
|
-
defaultRows: 24
|
|
1904
|
+
defaultRows: 24,
|
|
1905
|
+
_skipSpawnHelperCheck: false
|
|
1887
1906
|
};
|
|
1888
1907
|
var ensureSpawnHelperExecutable = () => {
|
|
1889
1908
|
if (spawnHelperChecked || process.platform === "win32") {
|
|
@@ -1956,7 +1975,9 @@ var createTmuxBackend = (deps = {}) => {
|
|
|
1956
1975
|
if (entry.pty) {
|
|
1957
1976
|
return entry.pty;
|
|
1958
1977
|
}
|
|
1959
|
-
|
|
1978
|
+
if (!runtime._skipSpawnHelperCheck) {
|
|
1979
|
+
ensureSpawnHelperExecutable();
|
|
1980
|
+
}
|
|
1960
1981
|
const ptyInstance = runtime.spawnPty("tmux", ["attach-session", "-t", entry.session.name], {
|
|
1961
1982
|
name: "xterm-256color",
|
|
1962
1983
|
cols: entry.cols,
|
|
@@ -2059,36 +2080,83 @@ var readLines = (data, carry, onLine) => {
|
|
|
2059
2080
|
}
|
|
2060
2081
|
return remainder;
|
|
2061
2082
|
};
|
|
2083
|
+
var normalizeLine = (line) => line.trim();
|
|
2062
2084
|
var createCloudflaredProvider = (deps = {}) => {
|
|
2063
2085
|
const spawn2 = deps.spawn ?? spawnCallback;
|
|
2064
2086
|
let child = null;
|
|
2065
|
-
const start = (localUrl) => {
|
|
2087
|
+
const start = (localUrl, options = {}) => {
|
|
2066
2088
|
if (child) {
|
|
2067
2089
|
return Promise.reject(new Error("cloudflared already running"));
|
|
2068
2090
|
}
|
|
2069
|
-
|
|
2091
|
+
const token = options.token?.trim();
|
|
2092
|
+
const publicUrl = options.publicUrl?.trim();
|
|
2093
|
+
if (token && !publicUrl) {
|
|
2094
|
+
return Promise.reject(new Error("tunnel public URL required when using tunnel token"));
|
|
2095
|
+
}
|
|
2096
|
+
const args = token ? ["tunnel", "run", "--token", token] : ["tunnel", "--url", localUrl];
|
|
2097
|
+
child = spawn2("cloudflared", args);
|
|
2070
2098
|
let stdoutCarry = "";
|
|
2071
2099
|
let stderrCarry = "";
|
|
2100
|
+
const errorLines = [];
|
|
2072
2101
|
return new Promise((resolve4, reject) => {
|
|
2102
|
+
let resolved = false;
|
|
2103
|
+
let resolveTimer = null;
|
|
2104
|
+
const resolveOnce = (url) => {
|
|
2105
|
+
if (resolved) {
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
resolved = true;
|
|
2109
|
+
if (resolveTimer) {
|
|
2110
|
+
clearTimeout(resolveTimer);
|
|
2111
|
+
resolveTimer = null;
|
|
2112
|
+
}
|
|
2113
|
+
resolve4({ publicUrl: url });
|
|
2114
|
+
};
|
|
2073
2115
|
const handleLine = (line) => {
|
|
2074
|
-
const
|
|
2116
|
+
const cleaned = normalizeLine(line);
|
|
2117
|
+
if (!cleaned) {
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
options.log?.(cleaned, "stdout");
|
|
2121
|
+
const url = parseCloudflaredUrl(cleaned);
|
|
2075
2122
|
if (url) {
|
|
2076
|
-
|
|
2123
|
+
resolveOnce(url);
|
|
2077
2124
|
}
|
|
2078
2125
|
};
|
|
2079
2126
|
const handleOutput = (data, isStdout) => {
|
|
2080
2127
|
if (isStdout) {
|
|
2081
2128
|
stdoutCarry = readLines(data.toString(), stdoutCarry, handleLine);
|
|
2082
2129
|
} else {
|
|
2083
|
-
stderrCarry = readLines(data.toString(), stderrCarry,
|
|
2130
|
+
stderrCarry = readLines(data.toString(), stderrCarry, (line) => {
|
|
2131
|
+
const cleaned = normalizeLine(line);
|
|
2132
|
+
if (!cleaned) {
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
options.log?.(cleaned, "stderr");
|
|
2136
|
+
errorLines.push(cleaned);
|
|
2137
|
+
if (errorLines.length > 6) {
|
|
2138
|
+
errorLines.shift();
|
|
2139
|
+
}
|
|
2140
|
+
const url = parseCloudflaredUrl(cleaned);
|
|
2141
|
+
if (url) {
|
|
2142
|
+
resolveOnce(url);
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2084
2145
|
}
|
|
2085
2146
|
};
|
|
2086
2147
|
child?.stdout.on("data", (data) => handleOutput(data, true));
|
|
2087
2148
|
child?.stderr.on("data", (data) => handleOutput(data, false));
|
|
2088
2149
|
child?.once("error", (error) => reject(error));
|
|
2089
2150
|
child?.once("exit", (code) => {
|
|
2090
|
-
|
|
2151
|
+
if (resolved) {
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
const suffix = errorLines.length > 0 ? `: ${errorLines.join(" | ")}` : "";
|
|
2155
|
+
reject(new Error(`cloudflared exited (${code ?? "unknown"})${suffix}`));
|
|
2091
2156
|
});
|
|
2157
|
+
if (token && publicUrl) {
|
|
2158
|
+
resolveTimer = setTimeout(() => resolveOnce(publicUrl), 1200);
|
|
2159
|
+
}
|
|
2092
2160
|
});
|
|
2093
2161
|
};
|
|
2094
2162
|
const stop = async () => {
|
|
@@ -2373,11 +2441,11 @@ var isAllowedOrigin = (origin, host) => {
|
|
|
2373
2441
|
};
|
|
2374
2442
|
var allowedControlKeys = new Set(TERMINAL_CONTROL_KEYS);
|
|
2375
2443
|
var parseClientMessage = (payload) => {
|
|
2376
|
-
const size = typeof payload === "string" ? payload.length : payload.byteLength;
|
|
2444
|
+
const size = typeof payload === "string" ? payload.length : Array.isArray(payload) ? payload.reduce((sum, buf) => sum + buf.length, 0) : payload.byteLength;
|
|
2377
2445
|
if (size > MAX_WS_MESSAGE_SIZE) {
|
|
2378
2446
|
return { ok: false, error: "too_large" };
|
|
2379
2447
|
}
|
|
2380
|
-
const text = typeof payload === "string" ? payload : payload.toString();
|
|
2448
|
+
const text = typeof payload === "string" ? payload : Array.isArray(payload) ? Buffer.concat(payload).toString() : payload.toString();
|
|
2381
2449
|
try {
|
|
2382
2450
|
const parsed = JSON.parse(text);
|
|
2383
2451
|
if (parsed.type === "input" && typeof parsed.data === "string") {
|
|
@@ -2660,6 +2728,18 @@ var parseSessionCount = (value) => {
|
|
|
2660
2728
|
}
|
|
2661
2729
|
return parsed;
|
|
2662
2730
|
};
|
|
2731
|
+
var normalizePublicUrl = (value) => {
|
|
2732
|
+
let parsed;
|
|
2733
|
+
try {
|
|
2734
|
+
parsed = new URL(value);
|
|
2735
|
+
} catch {
|
|
2736
|
+
throw new Error("invalid tunnel url");
|
|
2737
|
+
}
|
|
2738
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2739
|
+
throw new Error("invalid tunnel url");
|
|
2740
|
+
}
|
|
2741
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
2742
|
+
};
|
|
2663
2743
|
var startCommand = async (options, deps = {}) => {
|
|
2664
2744
|
const logger = deps.logger ?? createDefaultLogger();
|
|
2665
2745
|
const processRef = deps.process ?? process;
|
|
@@ -2674,6 +2754,16 @@ var startCommand = async (options, deps = {}) => {
|
|
|
2674
2754
|
const terminalBackend = (deps.createTerminalBackend ?? (() => createTmuxBackend()))();
|
|
2675
2755
|
const terminalRegistry = (deps.createTerminalRegistry ?? (() => createTerminalRegistry()))();
|
|
2676
2756
|
const tunnelProvider = (deps.createTunnelProvider ?? (() => createCloudflaredProvider()))();
|
|
2757
|
+
const tunnelTokenRaw = options.tunnelToken ?? env.TERMBRIDGE_TUNNEL_TOKEN;
|
|
2758
|
+
const tunnelToken = tunnelTokenRaw?.trim() || void 0;
|
|
2759
|
+
const tunnelUrlRaw = options.tunnelUrl ?? env.TERMBRIDGE_TUNNEL_URL;
|
|
2760
|
+
const tunnelUrl = tunnelToken && tunnelUrlRaw ? normalizePublicUrl(tunnelUrlRaw) : void 0;
|
|
2761
|
+
if (tunnelToken && !options.port) {
|
|
2762
|
+
throw new Error("port required when using tunnel token");
|
|
2763
|
+
}
|
|
2764
|
+
if (tunnelToken && !tunnelUrl) {
|
|
2765
|
+
throw new Error("tunnel url required when using tunnel token");
|
|
2766
|
+
}
|
|
2677
2767
|
const wsLimiter = createRateLimiter({ limit: 30, windowMs: 6e4 });
|
|
2678
2768
|
const serverFactory = deps.createServer ?? ((serverDeps) => createAppServer({
|
|
2679
2769
|
...serverDeps,
|
|
@@ -2704,7 +2794,10 @@ var startCommand = async (options, deps = {}) => {
|
|
|
2704
2794
|
const { token } = auth.issueToken();
|
|
2705
2795
|
let publicUrl = "";
|
|
2706
2796
|
try {
|
|
2707
|
-
const result = await tunnelProvider.start(localUrl
|
|
2797
|
+
const result = await tunnelProvider.start(localUrl, {
|
|
2798
|
+
token: tunnelToken,
|
|
2799
|
+
publicUrl: tunnelUrl
|
|
2800
|
+
});
|
|
2708
2801
|
publicUrl = result.publicUrl;
|
|
2709
2802
|
} catch (error) {
|
|
2710
2803
|
const message = error instanceof Error ? error.message : "unknown error";
|
|
@@ -2796,7 +2889,7 @@ var buildSessionName = (options, localUrl) => {
|
|
|
2796
2889
|
return "termbridge";
|
|
2797
2890
|
}
|
|
2798
2891
|
};
|
|
2799
|
-
var buildRedeemUrl = (result) => `${result.publicUrl}/s/${result.token}`;
|
|
2892
|
+
var buildRedeemUrl = (result) => `${result.publicUrl}/__tb/s/${result.token}`;
|
|
2800
2893
|
var generateQr = async (text) => new Promise((resolve4) => {
|
|
2801
2894
|
qrcode.generate(text, { small: true }, (output) => resolve4(output));
|
|
2802
2895
|
});
|