sub-bridge 1.0.5 → 1.1.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/tunnel/providers/cloudflare.d.ts +7 -1
- package/dist/tunnel/providers/cloudflare.d.ts.map +1 -1
- package/dist/tunnel/providers/cloudflare.js +154 -8
- package/dist/tunnel/providers/cloudflare.js.map +1 -1
- package/dist/tunnel/providers/index.d.ts +0 -2
- package/dist/tunnel/providers/index.d.ts.map +1 -1
- package/dist/tunnel/providers/index.js +1 -5
- package/dist/tunnel/providers/index.js.map +1 -1
- package/dist/tunnel/registry.d.ts.map +1 -1
- package/dist/tunnel/registry.js +5 -8
- package/dist/tunnel/registry.js.map +1 -1
- package/dist/tunnel/types.d.ts +9 -0
- package/dist/tunnel/types.d.ts.map +1 -1
- package/index.html +34 -30
- package/package.json +1 -1
- package/src/tunnel/providers/cloudflare.ts +178 -8
- package/src/tunnel/providers/index.ts +0 -2
- package/src/tunnel/registry.ts +6 -13
- package/src/tunnel/types.ts +10 -0
- package/dist/tunnel/providers/ngrok.d.ts +0 -10
- package/dist/tunnel/providers/ngrok.d.ts.map +0 -1
- package/dist/tunnel/providers/ngrok.js +0 -52
- package/dist/tunnel/providers/ngrok.js.map +0 -1
- package/dist/tunnel/providers/tailscale.d.ts +0 -10
- package/dist/tunnel/providers/tailscale.d.ts.map +0 -1
- package/dist/tunnel/providers/tailscale.js +0 -48
- package/dist/tunnel/providers/tailscale.js.map +0 -1
- package/src/tunnel/providers/ngrok.ts +0 -56
- package/src/tunnel/providers/tailscale.ts +0 -50
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import type { TunnelProvider, TunnelInstance } from '../types';
|
|
1
|
+
import type { TunnelProvider, TunnelInstance, NamedTunnelInfo } from '../types';
|
|
2
2
|
export declare class CloudflareTunnelProvider implements TunnelProvider {
|
|
3
3
|
id: string;
|
|
4
4
|
name: string;
|
|
5
5
|
supportsNamedTunnels: boolean;
|
|
6
6
|
isAvailable(): Promise<boolean>;
|
|
7
|
+
isAuthenticated(): Promise<boolean>;
|
|
8
|
+
listTunnels(): Promise<NamedTunnelInfo[]>;
|
|
9
|
+
private getTunnelHostname;
|
|
10
|
+
private runCloudflaredCommand;
|
|
7
11
|
start(localPort: number, namedUrl?: string): Promise<TunnelInstance>;
|
|
12
|
+
private startNamedTunnel;
|
|
13
|
+
private startQuickTunnel;
|
|
8
14
|
}
|
|
9
15
|
//# sourceMappingURL=cloudflare.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../../src/tunnel/providers/cloudflare.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../../src/tunnel/providers/cloudflare.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAS/E,qBAAa,wBAAyB,YAAW,cAAc;IAC7D,EAAE,SAAe;IACjB,IAAI,SAAe;IACnB,oBAAoB,UAAO;IAErB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAK/B,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAUnC,WAAW,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YA0BjC,iBAAiB;IAiB/B,OAAO,CAAC,qBAAqB;IAwBvB,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;YAO5D,gBAAgB;YAwFhB,gBAAgB;CAwE/B"}
|
|
@@ -8,9 +8,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.CloudflareTunnelProvider = void 0;
|
|
10
10
|
const cloudflared_1 = require("cloudflared");
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
12
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
12
13
|
const node_os_1 = __importDefault(require("node:os"));
|
|
13
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
function getCloudflaredConfigDir() {
|
|
16
|
+
if (process.platform === 'win32') {
|
|
17
|
+
return node_path_1.default.join(process.env.USERPROFILE || node_os_1.default.homedir(), '.cloudflared');
|
|
18
|
+
}
|
|
19
|
+
return node_path_1.default.join(node_os_1.default.homedir(), '.cloudflared');
|
|
20
|
+
}
|
|
14
21
|
class CloudflareTunnelProvider {
|
|
15
22
|
id = 'cloudflare';
|
|
16
23
|
name = 'Cloudflare';
|
|
@@ -19,16 +26,155 @@ class CloudflareTunnelProvider {
|
|
|
19
26
|
// The cloudflared npm package auto-installs the binary
|
|
20
27
|
return true;
|
|
21
28
|
}
|
|
29
|
+
async isAuthenticated() {
|
|
30
|
+
const certPath = node_path_1.default.join(getCloudflaredConfigDir(), 'cert.pem');
|
|
31
|
+
try {
|
|
32
|
+
await promises_1.default.access(certPath);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async listTunnels() {
|
|
40
|
+
if (!await this.isAuthenticated()) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
// Run cloudflared tunnel list and parse output
|
|
45
|
+
const output = await this.runCloudflaredCommand(['tunnel', 'list', '--output', 'json']);
|
|
46
|
+
const tunnels = JSON.parse(output);
|
|
47
|
+
// Get DNS routes for each tunnel
|
|
48
|
+
const results = [];
|
|
49
|
+
for (const tunnel of tunnels) {
|
|
50
|
+
const hostname = await this.getTunnelHostname(tunnel.id);
|
|
51
|
+
results.push({
|
|
52
|
+
id: tunnel.id,
|
|
53
|
+
name: tunnel.name,
|
|
54
|
+
hostname,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async getTunnelHostname(tunnelId) {
|
|
64
|
+
try {
|
|
65
|
+
// Check config.yml for ingress rules
|
|
66
|
+
const configPath = node_path_1.default.join(getCloudflaredConfigDir(), 'config.yml');
|
|
67
|
+
const config = await promises_1.default.readFile(configPath, 'utf-8');
|
|
68
|
+
// Simple regex to extract hostname from ingress rules
|
|
69
|
+
const hostnameMatch = config.match(/hostname:\s*(\S+)/);
|
|
70
|
+
if (hostnameMatch) {
|
|
71
|
+
return hostnameMatch[1];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Config file doesn't exist or is unreadable
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
runCloudflaredCommand(args) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const proc = (0, node_child_process_1.spawn)('cloudflared', args, {
|
|
82
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
83
|
+
});
|
|
84
|
+
let stdout = '';
|
|
85
|
+
let stderr = '';
|
|
86
|
+
proc.stdout.on('data', (data) => { stdout += data.toString(); });
|
|
87
|
+
proc.stderr.on('data', (data) => { stderr += data.toString(); });
|
|
88
|
+
proc.on('close', (code) => {
|
|
89
|
+
if (code === 0) {
|
|
90
|
+
resolve(stdout);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
reject(new Error(stderr || `cloudflared exited with code ${code}`));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
proc.on('error', reject);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
22
99
|
async start(localPort, namedUrl) {
|
|
23
100
|
if (namedUrl) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
101
|
+
return this.startNamedTunnel(localPort, namedUrl);
|
|
102
|
+
}
|
|
103
|
+
return this.startQuickTunnel(localPort);
|
|
104
|
+
}
|
|
105
|
+
async startNamedTunnel(localPort, namedUrl) {
|
|
106
|
+
// Check if authenticated
|
|
107
|
+
if (!await this.isAuthenticated()) {
|
|
108
|
+
throw new Error('Cloudflare authentication required. Run: cloudflared tunnel login');
|
|
109
|
+
}
|
|
110
|
+
// Find the tunnel info
|
|
111
|
+
const tunnels = await this.listTunnels();
|
|
112
|
+
const tunnel = tunnels.find(t => t.name === namedUrl ||
|
|
113
|
+
t.hostname === namedUrl ||
|
|
114
|
+
t.id === namedUrl);
|
|
115
|
+
if (!tunnel) {
|
|
116
|
+
throw new Error(`Tunnel not found: ${namedUrl}. Available: ${tunnels.map(t => t.name).join(', ') || 'none'}`);
|
|
31
117
|
}
|
|
118
|
+
if (!tunnel.hostname) {
|
|
119
|
+
throw new Error(`Tunnel "${tunnel.name}" has no DNS route configured. Configure one in Cloudflare dashboard.`);
|
|
120
|
+
}
|
|
121
|
+
// Create temp config with the requested port
|
|
122
|
+
const tmpDir = await promises_1.default.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'sub-bridge-cloudflared-'));
|
|
123
|
+
const credentialsFile = node_path_1.default.join(getCloudflaredConfigDir(), `${tunnel.id}.json`);
|
|
124
|
+
const configContent = `tunnel: ${tunnel.id}
|
|
125
|
+
credentials-file: ${credentialsFile}
|
|
126
|
+
protocol: http2
|
|
127
|
+
ingress:
|
|
128
|
+
- hostname: ${tunnel.hostname}
|
|
129
|
+
service: http://localhost:${localPort}
|
|
130
|
+
- service: http_status:404
|
|
131
|
+
`;
|
|
132
|
+
const configPath = node_path_1.default.join(tmpDir, 'config.yml');
|
|
133
|
+
await promises_1.default.writeFile(configPath, configContent);
|
|
134
|
+
// Spawn cloudflared tunnel run
|
|
135
|
+
const proc = (0, node_child_process_1.spawn)('cloudflared', ['tunnel', 'run', '--config', configPath], {
|
|
136
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
137
|
+
detached: false,
|
|
138
|
+
});
|
|
139
|
+
// Wait for connection
|
|
140
|
+
const publicUrl = `https://${tunnel.hostname}`;
|
|
141
|
+
await new Promise((resolve, reject) => {
|
|
142
|
+
const timeout = setTimeout(() => {
|
|
143
|
+
proc.kill();
|
|
144
|
+
reject(new Error('Named tunnel connection timeout (60s)'));
|
|
145
|
+
}, 60000);
|
|
146
|
+
let lastError = '';
|
|
147
|
+
proc.stderr.on('data', (data) => {
|
|
148
|
+
const line = data.toString();
|
|
149
|
+
if (line.includes('Registered tunnel connection')) {
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
resolve();
|
|
152
|
+
}
|
|
153
|
+
if (line.includes('ERR') || line.includes('error')) {
|
|
154
|
+
lastError = line.trim();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
proc.on('close', (code) => {
|
|
158
|
+
clearTimeout(timeout);
|
|
159
|
+
if (code !== 0) {
|
|
160
|
+
reject(new Error(lastError || `cloudflared exited with code ${code}`));
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
proc.on('error', (err) => {
|
|
164
|
+
clearTimeout(timeout);
|
|
165
|
+
reject(err);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
providerId: this.id,
|
|
170
|
+
publicUrl,
|
|
171
|
+
stop: () => {
|
|
172
|
+
proc.kill();
|
|
173
|
+
void promises_1.default.rm(tmpDir, { recursive: true, force: true });
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async startQuickTunnel(localPort) {
|
|
32
178
|
// Anonymous tunnel using cloudflared npm package
|
|
33
179
|
const tmpDir = await promises_1.default.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'sub-bridge-cloudflared-'));
|
|
34
180
|
const configPath = node_path_1.default.join(tmpDir, 'config.yml');
|
|
@@ -54,7 +200,7 @@ class CloudflareTunnelProvider {
|
|
|
54
200
|
if (data.includes('ERR') || data.includes('error')) {
|
|
55
201
|
// Extract meaningful error message
|
|
56
202
|
if (data.includes('Too Many Requests') || data.includes('1015')) {
|
|
57
|
-
lastError = 'Cloudflare rate limit exceeded. Please wait a few minutes
|
|
203
|
+
lastError = 'Cloudflare rate limit exceeded. Please wait a few minutes and try again.';
|
|
58
204
|
}
|
|
59
205
|
else if (data.includes('failed to unmarshal')) {
|
|
60
206
|
lastError = data.split('\n').find(line => line.includes('failed'))?.trim() || data;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../src/tunnel/providers/cloudflare.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,6BAA6B;AAC7B,+EAA+E;;;;;;AAE/E,6CAAoC;AACpC,gEAAiC;AACjC,sDAAwB;AACxB,0DAA4B;AAG5B,MAAa,wBAAwB;IACnC,EAAE,GAAG,YAAY,CAAA;IACjB,IAAI,GAAG,YAAY,CAAA;IACnB,oBAAoB,GAAG,IAAI,CAAA;IAE3B,KAAK,CAAC,WAAW;QACf,uDAAuD;QACvD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB,EAAE,QAAiB;QAC9C,IAAI,QAAQ,EAAE,CAAC;YACb,
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../src/tunnel/providers/cloudflare.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tunnel/providers/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tunnel/providers/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
// Tunnel Providers Index
|
|
4
4
|
// ============================================================================
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.CloudflareTunnelProvider = void 0;
|
|
7
7
|
var cloudflare_1 = require("./cloudflare");
|
|
8
8
|
Object.defineProperty(exports, "CloudflareTunnelProvider", { enumerable: true, get: function () { return cloudflare_1.CloudflareTunnelProvider; } });
|
|
9
|
-
var ngrok_1 = require("./ngrok");
|
|
10
|
-
Object.defineProperty(exports, "NgrokTunnelProvider", { enumerable: true, get: function () { return ngrok_1.NgrokTunnelProvider; } });
|
|
11
|
-
var tailscale_1 = require("./tailscale");
|
|
12
|
-
Object.defineProperty(exports, "TailscaleTunnelProvider", { enumerable: true, get: function () { return tailscale_1.TailscaleTunnelProvider; } });
|
|
13
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tunnel/providers/index.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,yBAAyB;AACzB,+EAA+E;;;AAE/E,2CAAuD;AAA9C,sHAAA,wBAAwB,OAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tunnel/providers/index.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,yBAAyB;AACzB,+EAA+E;;;AAE/E,2CAAuD;AAA9C,sHAAA,wBAAwB,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/tunnel/registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAkC,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/tunnel/registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAkC,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAmDzF,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,SAAS,CAAsB;;IAOjC,YAAY,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAgB7C,SAAS,IAAI,YAAY;IAenB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAkD5F,IAAI,IAAI,YAAY;IASpB,YAAY,IAAI,MAAM,GAAG,IAAI;CAG9B"}
|
package/dist/tunnel/registry.js
CHANGED
|
@@ -58,23 +58,20 @@ class TunnelRegistry {
|
|
|
58
58
|
startedAt = null;
|
|
59
59
|
lastError = null;
|
|
60
60
|
constructor() {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
new providers_1.NgrokTunnelProvider(),
|
|
64
|
-
new providers_1.TailscaleTunnelProvider(),
|
|
65
|
-
];
|
|
66
|
-
for (const provider of allProviders) {
|
|
67
|
-
this.providers.set(provider.id, provider);
|
|
68
|
-
}
|
|
61
|
+
const provider = new providers_1.CloudflareTunnelProvider();
|
|
62
|
+
this.providers.set(provider.id, provider);
|
|
69
63
|
}
|
|
70
64
|
async getProviders() {
|
|
71
65
|
const results = [];
|
|
72
66
|
for (const [id, provider] of this.providers) {
|
|
67
|
+
const authenticated = await provider.isAuthenticated();
|
|
73
68
|
results.push({
|
|
74
69
|
id,
|
|
75
70
|
name: provider.name,
|
|
76
71
|
available: await provider.isAvailable(),
|
|
77
72
|
supportsNamedTunnels: provider.supportsNamedTunnels,
|
|
73
|
+
authenticated,
|
|
74
|
+
namedTunnels: authenticated ? await provider.listTunnels() : undefined,
|
|
78
75
|
});
|
|
79
76
|
}
|
|
80
77
|
return results;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/tunnel/registry.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,wDAAwD;AACxD,+EAA+E;;;AAG/E,wCAAkD;AAElD,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAC1D,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,SAAiB,EAAE,YAAoB;IACvE,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAC5C,MAAM,SAAS,GAAG,GAAG,OAAO,SAAS,CAAA;IACrC,MAAM,WAAW,GAAG,EAAE,CAAA;IACtB,IAAI,SAAS,GAAiB,IAAI,CAAA;IAElC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACxD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;QACxC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAA;QAE1D,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAA;YACtE,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;YAClE,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAyC,CAAA;YACzE,IAAI,IAAI,CAAC,OAAO,KAAK,yBAAkB,EAAE,CAAC;gBACxC,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;YAC7D,CAAC;YACD,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAChE,MAAM,IAAI,KAAK,CAAC,wCAAwC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;YACvE,CAAC;YACD,OAAM;QACR,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC1D,SAAS,GAAG,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;YACjD,CAAC;iBAAM,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;gBAClC,SAAS,GAAG,KAAK,CAAA;YACnB,CAAC;iBAAM,CAAC;gBACN,SAAS,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;YACtC,CAAC;YACD,IAAI,OAAO,GAAG,WAAW,EAAE,CAAC;gBAC1B,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;gBAChB,SAAQ;YACV,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,wBAAwB,SAAS,CAAC,OAAO,KAAK,SAAS,GAAG,CAAC,CAAA;QAC7E,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,OAAO,CAAC,CAAA;QACvB,CAAC;IACH,CAAC;AACH,CAAC;AACD,
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/tunnel/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/tunnel/types.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
export interface NamedTunnelInfo {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
hostname?: string;
|
|
5
|
+
}
|
|
1
6
|
export interface TunnelProvider {
|
|
2
7
|
id: string;
|
|
3
8
|
name: string;
|
|
4
9
|
supportsNamedTunnels: boolean;
|
|
5
10
|
isAvailable(): Promise<boolean>;
|
|
11
|
+
isAuthenticated(): Promise<boolean>;
|
|
12
|
+
listTunnels(): Promise<NamedTunnelInfo[]>;
|
|
6
13
|
start(localPort: number, namedUrl?: string): Promise<TunnelInstance>;
|
|
7
14
|
}
|
|
8
15
|
export interface TunnelInstance {
|
|
@@ -22,5 +29,7 @@ export interface ProviderInfo {
|
|
|
22
29
|
name: string;
|
|
23
30
|
available: boolean;
|
|
24
31
|
supportsNamedTunnels: boolean;
|
|
32
|
+
authenticated?: boolean;
|
|
33
|
+
namedTunnels?: NamedTunnelInfo[];
|
|
25
34
|
}
|
|
26
35
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tunnel/types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,oBAAoB,EAAE,OAAO,CAAA;IAC7B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/B,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;CACrE;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,IAAI,IAAI,CAAA;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,OAAO,CAAA;IAClB,oBAAoB,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tunnel/types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,oBAAoB,EAAE,OAAO,CAAA;IAC7B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/B,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACnC,WAAW,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IACzC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;CACrE;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,IAAI,IAAI,CAAA;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,OAAO,CAAA;IAClB,oBAAoB,EAAE,OAAO,CAAA;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,YAAY,CAAC,EAAE,eAAe,EAAE,CAAA;CACjC"}
|
package/index.html
CHANGED
|
@@ -240,39 +240,39 @@
|
|
|
240
240
|
</template>
|
|
241
241
|
</div>
|
|
242
242
|
|
|
243
|
-
<
|
|
244
|
-
<
|
|
245
|
-
class="text-[10px] font-semibold uppercase tracking-wider text-neutral-500"
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
class="w-full rounded-lg border border-neutral-800 bg-neutral-950 px-3 py-2 text-sm text-neutral-100 placeholder:text-neutral-600 focus:border-neutral-600 focus:outline-none font-mono"
|
|
256
|
-
>
|
|
257
|
-
</div>
|
|
243
|
+
<template x-if="hasTunnel">
|
|
244
|
+
<div class="space-y-1">
|
|
245
|
+
<label class="text-[10px] font-semibold uppercase tracking-wider text-neutral-500">OpenAI Base URL</label>
|
|
246
|
+
<input
|
|
247
|
+
type="text"
|
|
248
|
+
:value="effectivePublicUrl + '/v1'"
|
|
249
|
+
readonly
|
|
250
|
+
@focus="$el.select()"
|
|
251
|
+
class="w-full rounded-lg border border-neutral-800 bg-neutral-950 px-3 py-2 text-sm text-neutral-100 focus:border-neutral-600 focus:outline-none font-mono"
|
|
252
|
+
>
|
|
253
|
+
</div>
|
|
254
|
+
</template>
|
|
258
255
|
|
|
259
256
|
<template x-if="!hasTunnel">
|
|
260
257
|
<div class="space-y-3">
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
258
|
+
<!-- Named tunnel selector (only when authenticated) -->
|
|
259
|
+
<template x-if="selectedProvider?.authenticated && selectedProvider?.namedTunnels?.length">
|
|
260
|
+
<div class="space-y-2">
|
|
261
|
+
<label class="text-[10px] font-semibold uppercase tracking-wider text-neutral-500">Use Named Tunnel (optional)</label>
|
|
262
|
+
<select
|
|
263
|
+
x-model="tunnel.customDomain"
|
|
264
|
+
class="w-full rounded-lg border border-neutral-800 bg-neutral-950 px-3 py-2 text-sm text-neutral-100 focus:border-neutral-600 focus:outline-none"
|
|
265
|
+
>
|
|
266
|
+
<option value="">Quick tunnel (random URL)</option>
|
|
267
|
+
<template x-for="t in selectedProvider.namedTunnels" :key="t.id">
|
|
268
|
+
<option :value="t.hostname || t.name" x-text="t.hostname ? t.name + ' (' + t.hostname + ')' : t.name"></option>
|
|
269
|
+
</template>
|
|
270
|
+
</select>
|
|
271
271
|
</div>
|
|
272
|
+
</template>
|
|
273
|
+
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
272
274
|
<div class="flex flex-wrap gap-2" x-show="tunnel.loading">
|
|
273
275
|
<span class="h-7 w-20 rounded-lg border border-neutral-800 bg-neutral-900/60 animate-pulse"></span>
|
|
274
|
-
<span class="h-7 w-16 rounded-lg border border-neutral-800 bg-neutral-900/60 animate-pulse"></span>
|
|
275
|
-
<span class="h-7 w-12 rounded-lg border border-neutral-800 bg-neutral-900/60 animate-pulse"></span>
|
|
276
276
|
</div>
|
|
277
277
|
<template x-if="!tunnel.status.active && !hasExternalUrl">
|
|
278
278
|
<button
|
|
@@ -285,7 +285,7 @@
|
|
|
285
285
|
</template>
|
|
286
286
|
</div>
|
|
287
287
|
<p x-show="!tunnel.providers.some(p => p.available) && !tunnel.loading" class="text-xs text-neutral-400">
|
|
288
|
-
No tunnel providers found
|
|
288
|
+
No tunnel providers found. Install cloudflared to create tunnels.
|
|
289
289
|
</p>
|
|
290
290
|
</div>
|
|
291
291
|
</template>
|
|
@@ -514,10 +514,14 @@
|
|
|
514
514
|
return this.tunnel.status.active || this.hasExternalUrl;
|
|
515
515
|
},
|
|
516
516
|
|
|
517
|
+
// Selected tunnel provider object
|
|
518
|
+
get selectedProvider() {
|
|
519
|
+
return this.tunnel.providers.find(p => p.id === this.tunnel.selectedProvider) || null;
|
|
520
|
+
},
|
|
521
|
+
|
|
517
522
|
// Selected tunnel provider name (falls back to generic label)
|
|
518
523
|
get selectedTunnelName() {
|
|
519
|
-
|
|
520
|
-
return selected ? selected.name : 'Tunnel';
|
|
524
|
+
return this.selectedProvider ? this.selectedProvider.name : 'Tunnel';
|
|
521
525
|
},
|
|
522
526
|
|
|
523
527
|
get firstClaudeAccountId() {
|
package/package.json
CHANGED
|
@@ -3,10 +3,18 @@
|
|
|
3
3
|
// ============================================================================
|
|
4
4
|
|
|
5
5
|
import { Tunnel } from 'cloudflared'
|
|
6
|
+
import { spawn } from 'node:child_process'
|
|
6
7
|
import fs from 'node:fs/promises'
|
|
7
8
|
import os from 'node:os'
|
|
8
9
|
import path from 'node:path'
|
|
9
|
-
import type { TunnelProvider, TunnelInstance } from '../types'
|
|
10
|
+
import type { TunnelProvider, TunnelInstance, NamedTunnelInfo } from '../types'
|
|
11
|
+
|
|
12
|
+
function getCloudflaredConfigDir(): string {
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
return path.join(process.env.USERPROFILE || os.homedir(), '.cloudflared')
|
|
15
|
+
}
|
|
16
|
+
return path.join(os.homedir(), '.cloudflared')
|
|
17
|
+
}
|
|
10
18
|
|
|
11
19
|
export class CloudflareTunnelProvider implements TunnelProvider {
|
|
12
20
|
id = 'cloudflare'
|
|
@@ -18,17 +26,179 @@ export class CloudflareTunnelProvider implements TunnelProvider {
|
|
|
18
26
|
return true
|
|
19
27
|
}
|
|
20
28
|
|
|
29
|
+
async isAuthenticated(): Promise<boolean> {
|
|
30
|
+
const certPath = path.join(getCloudflaredConfigDir(), 'cert.pem')
|
|
31
|
+
try {
|
|
32
|
+
await fs.access(certPath)
|
|
33
|
+
return true
|
|
34
|
+
} catch {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async listTunnels(): Promise<NamedTunnelInfo[]> {
|
|
40
|
+
if (!await this.isAuthenticated()) {
|
|
41
|
+
return []
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Run cloudflared tunnel list and parse output
|
|
46
|
+
const output = await this.runCloudflaredCommand(['tunnel', 'list', '--output', 'json'])
|
|
47
|
+
const tunnels = JSON.parse(output) as Array<{ id: string; name: string }>
|
|
48
|
+
|
|
49
|
+
// Get DNS routes for each tunnel
|
|
50
|
+
const results: NamedTunnelInfo[] = []
|
|
51
|
+
for (const tunnel of tunnels) {
|
|
52
|
+
const hostname = await this.getTunnelHostname(tunnel.id)
|
|
53
|
+
results.push({
|
|
54
|
+
id: tunnel.id,
|
|
55
|
+
name: tunnel.name,
|
|
56
|
+
hostname,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
return results
|
|
60
|
+
} catch {
|
|
61
|
+
return []
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async getTunnelHostname(tunnelId: string): Promise<string | undefined> {
|
|
66
|
+
try {
|
|
67
|
+
// Check config.yml for ingress rules
|
|
68
|
+
const configPath = path.join(getCloudflaredConfigDir(), 'config.yml')
|
|
69
|
+
const config = await fs.readFile(configPath, 'utf-8')
|
|
70
|
+
|
|
71
|
+
// Simple regex to extract hostname from ingress rules
|
|
72
|
+
const hostnameMatch = config.match(/hostname:\s*(\S+)/)
|
|
73
|
+
if (hostnameMatch) {
|
|
74
|
+
return hostnameMatch[1]
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Config file doesn't exist or is unreadable
|
|
78
|
+
}
|
|
79
|
+
return undefined
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private runCloudflaredCommand(args: string[]): Promise<string> {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const proc = spawn('cloudflared', args, {
|
|
85
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
let stdout = ''
|
|
89
|
+
let stderr = ''
|
|
90
|
+
|
|
91
|
+
proc.stdout.on('data', (data) => { stdout += data.toString() })
|
|
92
|
+
proc.stderr.on('data', (data) => { stderr += data.toString() })
|
|
93
|
+
|
|
94
|
+
proc.on('close', (code) => {
|
|
95
|
+
if (code === 0) {
|
|
96
|
+
resolve(stdout)
|
|
97
|
+
} else {
|
|
98
|
+
reject(new Error(stderr || `cloudflared exited with code ${code}`))
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
proc.on('error', reject)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
21
106
|
async start(localPort: number, namedUrl?: string): Promise<TunnelInstance> {
|
|
22
107
|
if (namedUrl) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
108
|
+
return this.startNamedTunnel(localPort, namedUrl)
|
|
109
|
+
}
|
|
110
|
+
return this.startQuickTunnel(localPort)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private async startNamedTunnel(localPort: number, namedUrl: string): Promise<TunnelInstance> {
|
|
114
|
+
// Check if authenticated
|
|
115
|
+
if (!await this.isAuthenticated()) {
|
|
116
|
+
throw new Error('Cloudflare authentication required. Run: cloudflared tunnel login')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Find the tunnel info
|
|
120
|
+
const tunnels = await this.listTunnels()
|
|
121
|
+
const tunnel = tunnels.find(t =>
|
|
122
|
+
t.name === namedUrl ||
|
|
123
|
+
t.hostname === namedUrl ||
|
|
124
|
+
t.id === namedUrl
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if (!tunnel) {
|
|
128
|
+
throw new Error(`Tunnel not found: ${namedUrl}. Available: ${tunnels.map(t => t.name).join(', ') || 'none'}`)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!tunnel.hostname) {
|
|
132
|
+
throw new Error(`Tunnel "${tunnel.name}" has no DNS route configured. Configure one in Cloudflare dashboard.`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Create temp config with the requested port
|
|
136
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sub-bridge-cloudflared-'))
|
|
137
|
+
const credentialsFile = path.join(getCloudflaredConfigDir(), `${tunnel.id}.json`)
|
|
138
|
+
|
|
139
|
+
const configContent = `tunnel: ${tunnel.id}
|
|
140
|
+
credentials-file: ${credentialsFile}
|
|
141
|
+
protocol: http2
|
|
142
|
+
ingress:
|
|
143
|
+
- hostname: ${tunnel.hostname}
|
|
144
|
+
service: http://localhost:${localPort}
|
|
145
|
+
- service: http_status:404
|
|
146
|
+
`
|
|
147
|
+
const configPath = path.join(tmpDir, 'config.yml')
|
|
148
|
+
await fs.writeFile(configPath, configContent)
|
|
149
|
+
|
|
150
|
+
// Spawn cloudflared tunnel run
|
|
151
|
+
const proc = spawn('cloudflared', ['tunnel', 'run', '--config', configPath], {
|
|
152
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
153
|
+
detached: false,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// Wait for connection
|
|
157
|
+
const publicUrl = `https://${tunnel.hostname}`
|
|
158
|
+
|
|
159
|
+
await new Promise<void>((resolve, reject) => {
|
|
160
|
+
const timeout = setTimeout(() => {
|
|
161
|
+
proc.kill()
|
|
162
|
+
reject(new Error('Named tunnel connection timeout (60s)'))
|
|
163
|
+
}, 60000)
|
|
164
|
+
|
|
165
|
+
let lastError = ''
|
|
166
|
+
|
|
167
|
+
proc.stderr.on('data', (data: Buffer) => {
|
|
168
|
+
const line = data.toString()
|
|
169
|
+
if (line.includes('Registered tunnel connection')) {
|
|
170
|
+
clearTimeout(timeout)
|
|
171
|
+
resolve()
|
|
172
|
+
}
|
|
173
|
+
if (line.includes('ERR') || line.includes('error')) {
|
|
174
|
+
lastError = line.trim()
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
proc.on('close', (code) => {
|
|
179
|
+
clearTimeout(timeout)
|
|
180
|
+
if (code !== 0) {
|
|
181
|
+
reject(new Error(lastError || `cloudflared exited with code ${code}`))
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
proc.on('error', (err) => {
|
|
186
|
+
clearTimeout(timeout)
|
|
187
|
+
reject(err)
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
providerId: this.id,
|
|
193
|
+
publicUrl,
|
|
194
|
+
stop: () => {
|
|
195
|
+
proc.kill()
|
|
196
|
+
void fs.rm(tmpDir, { recursive: true, force: true })
|
|
29
197
|
}
|
|
30
198
|
}
|
|
199
|
+
}
|
|
31
200
|
|
|
201
|
+
private async startQuickTunnel(localPort: number): Promise<TunnelInstance> {
|
|
32
202
|
// Anonymous tunnel using cloudflared npm package
|
|
33
203
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sub-bridge-cloudflared-'))
|
|
34
204
|
const configPath = path.join(tmpDir, 'config.yml')
|
|
@@ -57,7 +227,7 @@ export class CloudflareTunnelProvider implements TunnelProvider {
|
|
|
57
227
|
if (data.includes('ERR') || data.includes('error')) {
|
|
58
228
|
// Extract meaningful error message
|
|
59
229
|
if (data.includes('Too Many Requests') || data.includes('1015')) {
|
|
60
|
-
lastError = 'Cloudflare rate limit exceeded. Please wait a few minutes
|
|
230
|
+
lastError = 'Cloudflare rate limit exceeded. Please wait a few minutes and try again.'
|
|
61
231
|
} else if (data.includes('failed to unmarshal')) {
|
|
62
232
|
lastError = data.split('\n').find(line => line.includes('failed'))?.trim() || data
|
|
63
233
|
} else {
|
package/src/tunnel/registry.ts
CHANGED
|
@@ -51,11 +51,7 @@ async function verifyTunnelHealth(publicUrl: string, expectedPort: number): Prom
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
import {
|
|
55
|
-
CloudflareTunnelProvider,
|
|
56
|
-
NgrokTunnelProvider,
|
|
57
|
-
TailscaleTunnelProvider,
|
|
58
|
-
} from './providers'
|
|
54
|
+
import { CloudflareTunnelProvider } from './providers'
|
|
59
55
|
|
|
60
56
|
export class TunnelRegistry {
|
|
61
57
|
private providers: Map<string, TunnelProvider> = new Map()
|
|
@@ -64,24 +60,21 @@ export class TunnelRegistry {
|
|
|
64
60
|
private lastError: string | null = null
|
|
65
61
|
|
|
66
62
|
constructor() {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
new NgrokTunnelProvider(),
|
|
70
|
-
new TailscaleTunnelProvider(),
|
|
71
|
-
]
|
|
72
|
-
for (const provider of allProviders) {
|
|
73
|
-
this.providers.set(provider.id, provider)
|
|
74
|
-
}
|
|
63
|
+
const provider = new CloudflareTunnelProvider()
|
|
64
|
+
this.providers.set(provider.id, provider)
|
|
75
65
|
}
|
|
76
66
|
|
|
77
67
|
async getProviders(): Promise<ProviderInfo[]> {
|
|
78
68
|
const results: ProviderInfo[] = []
|
|
79
69
|
for (const [id, provider] of this.providers) {
|
|
70
|
+
const authenticated = await provider.isAuthenticated()
|
|
80
71
|
results.push({
|
|
81
72
|
id,
|
|
82
73
|
name: provider.name,
|
|
83
74
|
available: await provider.isAvailable(),
|
|
84
75
|
supportsNamedTunnels: provider.supportsNamedTunnels,
|
|
76
|
+
authenticated,
|
|
77
|
+
namedTunnels: authenticated ? await provider.listTunnels() : undefined,
|
|
85
78
|
})
|
|
86
79
|
}
|
|
87
80
|
return results
|
package/src/tunnel/types.ts
CHANGED
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
// Tunnel Types
|
|
3
3
|
// ============================================================================
|
|
4
4
|
|
|
5
|
+
export interface NamedTunnelInfo {
|
|
6
|
+
id: string
|
|
7
|
+
name: string
|
|
8
|
+
hostname?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
export interface TunnelProvider {
|
|
6
12
|
id: string
|
|
7
13
|
name: string
|
|
8
14
|
supportsNamedTunnels: boolean
|
|
9
15
|
isAvailable(): Promise<boolean>
|
|
16
|
+
isAuthenticated(): Promise<boolean>
|
|
17
|
+
listTunnels(): Promise<NamedTunnelInfo[]>
|
|
10
18
|
start(localPort: number, namedUrl?: string): Promise<TunnelInstance>
|
|
11
19
|
}
|
|
12
20
|
|
|
@@ -29,4 +37,6 @@ export interface ProviderInfo {
|
|
|
29
37
|
name: string
|
|
30
38
|
available: boolean
|
|
31
39
|
supportsNamedTunnels: boolean
|
|
40
|
+
authenticated?: boolean
|
|
41
|
+
namedTunnels?: NamedTunnelInfo[]
|
|
32
42
|
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { TunnelProvider, TunnelInstance } from '../types';
|
|
2
|
-
export declare class NgrokTunnelProvider implements TunnelProvider {
|
|
3
|
-
id: string;
|
|
4
|
-
name: string;
|
|
5
|
-
supportsNamedTunnels: boolean;
|
|
6
|
-
private process;
|
|
7
|
-
isAvailable(): Promise<boolean>;
|
|
8
|
-
start(localPort: number, namedUrl?: string): Promise<TunnelInstance>;
|
|
9
|
-
}
|
|
10
|
-
//# sourceMappingURL=ngrok.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ngrok.d.ts","sourceRoot":"","sources":["../../../src/tunnel/providers/ngrok.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAG9D,qBAAa,mBAAoB,YAAW,cAAc;IACxD,EAAE,SAAU;IACZ,IAAI,SAAU;IACd,oBAAoB,UAAO;IAC3B,OAAO,CAAC,OAAO,CAA4B;IAErC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CAqC3E"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// ============================================================================
|
|
3
|
-
// ngrok Tunnel Provider
|
|
4
|
-
// ============================================================================
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.NgrokTunnelProvider = void 0;
|
|
7
|
-
const utils_1 = require("../utils");
|
|
8
|
-
class NgrokTunnelProvider {
|
|
9
|
-
id = 'ngrok';
|
|
10
|
-
name = 'ngrok';
|
|
11
|
-
supportsNamedTunnels = true;
|
|
12
|
-
process = null;
|
|
13
|
-
async isAvailable() {
|
|
14
|
-
return (0, utils_1.binaryExists)('ngrok');
|
|
15
|
-
}
|
|
16
|
-
async start(localPort, namedUrl) {
|
|
17
|
-
const args = ['http', String(localPort)];
|
|
18
|
-
// Add subdomain if provided (requires ngrok account)
|
|
19
|
-
if (namedUrl) {
|
|
20
|
-
const subdomain = namedUrl.replace(/\.ngrok\.(io|app)$/, '');
|
|
21
|
-
args.push('--subdomain', subdomain);
|
|
22
|
-
}
|
|
23
|
-
this.process = (0, utils_1.spawnTunnelProcess)('ngrok', args);
|
|
24
|
-
// Poll ngrok's local API for tunnel URL
|
|
25
|
-
const url = await (0, utils_1.pollUntil)(async () => {
|
|
26
|
-
try {
|
|
27
|
-
const response = await fetch('http://127.0.0.1:4040/api/tunnels');
|
|
28
|
-
if (!response.ok)
|
|
29
|
-
return null;
|
|
30
|
-
const data = await response.json();
|
|
31
|
-
const httpsTunnel = data.tunnels.find(t => t.public_url.startsWith('https://'));
|
|
32
|
-
return httpsTunnel?.public_url || null;
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}, 30000).catch(() => {
|
|
38
|
-
throw new Error('ngrok tunnel timeout');
|
|
39
|
-
});
|
|
40
|
-
const proc = this.process;
|
|
41
|
-
return {
|
|
42
|
-
providerId: this.id,
|
|
43
|
-
publicUrl: url,
|
|
44
|
-
stop: () => {
|
|
45
|
-
proc?.kill('SIGTERM');
|
|
46
|
-
this.process = null;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
exports.NgrokTunnelProvider = NgrokTunnelProvider;
|
|
52
|
-
//# sourceMappingURL=ngrok.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ngrok.js","sourceRoot":"","sources":["../../../src/tunnel/providers/ngrok.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,wBAAwB;AACxB,+EAA+E;;;AAI/E,oCAAsE;AAEtE,MAAa,mBAAmB;IAC9B,EAAE,GAAG,OAAO,CAAA;IACZ,IAAI,GAAG,OAAO,CAAA;IACd,oBAAoB,GAAG,IAAI,CAAA;IACnB,OAAO,GAAwB,IAAI,CAAA;IAE3C,KAAK,CAAC,WAAW;QACf,OAAO,IAAA,oBAAY,EAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB,EAAE,QAAiB;QAC9C,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAA;QAExC,qDAAqD;QACrD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAA;YAC5D,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;QACrC,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,IAAA,0BAAkB,EAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QAEhD,wCAAwC;QACxC,MAAM,GAAG,GAAG,MAAM,IAAA,iBAAS,EAAC,KAAK,IAAI,EAAE;YACrC,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,mCAAmC,CAAC,CAAA;gBACjE,IAAI,CAAC,QAAQ,CAAC,EAAE;oBAAE,OAAO,IAAI,CAAA;gBAE7B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAgD,CAAA;gBAChF,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAA;gBAC/E,OAAO,WAAW,EAAE,UAAU,IAAI,IAAI,CAAA;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACnB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAA;QACzC,CAAC,CAAC,CAAA;QAEF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAA;QACzB,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,EAAE;YACnB,SAAS,EAAE,GAAG;YACd,IAAI,EAAE,GAAG,EAAE;gBACT,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;gBACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;YACrB,CAAC;SACF,CAAA;IACH,CAAC;CACF;AA/CD,kDA+CC"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { TunnelProvider, TunnelInstance } from '../types';
|
|
2
|
-
export declare class TailscaleTunnelProvider implements TunnelProvider {
|
|
3
|
-
id: string;
|
|
4
|
-
name: string;
|
|
5
|
-
supportsNamedTunnels: boolean;
|
|
6
|
-
private process;
|
|
7
|
-
isAvailable(): Promise<boolean>;
|
|
8
|
-
start(localPort: number): Promise<TunnelInstance>;
|
|
9
|
-
}
|
|
10
|
-
//# sourceMappingURL=tailscale.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tailscale.d.ts","sourceRoot":"","sources":["../../../src/tunnel/providers/tailscale.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAG9D,qBAAa,uBAAwB,YAAW,cAAc;IAC5D,EAAE,SAAc;IAChB,IAAI,SAAqB;IACzB,oBAAoB,UAAQ;IAC5B,OAAO,CAAC,OAAO,CAA4B;IAErC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAW/B,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CAwBxD"}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// ============================================================================
|
|
3
|
-
// Tailscale Funnel Provider
|
|
4
|
-
// ============================================================================
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.TailscaleTunnelProvider = void 0;
|
|
7
|
-
const child_process_1 = require("child_process");
|
|
8
|
-
const utils_1 = require("../utils");
|
|
9
|
-
class TailscaleTunnelProvider {
|
|
10
|
-
id = 'tailscale';
|
|
11
|
-
name = 'Tailscale Funnel';
|
|
12
|
-
supportsNamedTunnels = false;
|
|
13
|
-
process = null;
|
|
14
|
-
async isAvailable() {
|
|
15
|
-
if (!await (0, utils_1.binaryExists)('tailscale'))
|
|
16
|
-
return false;
|
|
17
|
-
try {
|
|
18
|
-
const status = await (0, utils_1.execJson)('tailscale status --json');
|
|
19
|
-
return status.BackendState === 'Running';
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
async start(localPort) {
|
|
26
|
-
// Get hostname from Tailscale status
|
|
27
|
-
const status = await (0, utils_1.execJson)('tailscale status --json');
|
|
28
|
-
const hostname = status.Self?.DNSName?.replace(/\.$/, '');
|
|
29
|
-
if (!hostname)
|
|
30
|
-
throw new Error('Could not determine Tailscale hostname');
|
|
31
|
-
// Start funnel
|
|
32
|
-
this.process = (0, utils_1.spawnTunnelProcess)('tailscale', ['funnel', String(localPort)]);
|
|
33
|
-
// Wait for funnel to initialize
|
|
34
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
35
|
-
const proc = this.process;
|
|
36
|
-
return {
|
|
37
|
-
providerId: this.id,
|
|
38
|
-
publicUrl: `https://${hostname}`,
|
|
39
|
-
stop: () => {
|
|
40
|
-
(0, child_process_1.spawn)('tailscale', ['funnel', 'off'], { stdio: 'ignore' });
|
|
41
|
-
proc?.kill('SIGTERM');
|
|
42
|
-
this.process = null;
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
exports.TailscaleTunnelProvider = TailscaleTunnelProvider;
|
|
48
|
-
//# sourceMappingURL=tailscale.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tailscale.js","sourceRoot":"","sources":["../../../src/tunnel/providers/tailscale.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,4BAA4B;AAC5B,+EAA+E;;;AAE/E,iDAAmD;AAEnD,oCAAqE;AAErE,MAAa,uBAAuB;IAClC,EAAE,GAAG,WAAW,CAAA;IAChB,IAAI,GAAG,kBAAkB,CAAA;IACzB,oBAAoB,GAAG,KAAK,CAAA;IACpB,OAAO,GAAwB,IAAI,CAAA;IAE3C,KAAK,CAAC,WAAW;QACf,IAAI,CAAC,MAAM,IAAA,oBAAY,EAAC,WAAW,CAAC;YAAE,OAAO,KAAK,CAAA;QAElD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAA,gBAAQ,EAA2B,yBAAyB,CAAC,CAAA;YAClF,OAAO,MAAM,CAAC,YAAY,KAAK,SAAS,CAAA;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB;QAC3B,qCAAqC;QACrC,MAAM,MAAM,GAAG,MAAM,IAAA,gBAAQ,EAAkC,yBAAyB,CAAC,CAAA;QACzF,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QAEzD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;QAExE,eAAe;QACf,IAAI,CAAC,OAAO,GAAG,IAAA,0BAAkB,EAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;QAE7E,gCAAgC;QAChC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QAE3C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAA;QACzB,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,EAAE;YACnB,SAAS,EAAE,WAAW,QAAQ,EAAE;YAChC,IAAI,EAAE,GAAG,EAAE;gBACT,IAAA,qBAAK,EAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;gBAC1D,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;gBACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;YACrB,CAAC;SACF,CAAA;IACH,CAAC;CACF;AAzCD,0DAyCC"}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// ngrok Tunnel Provider
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
|
-
import { ChildProcess } from 'child_process'
|
|
6
|
-
import type { TunnelProvider, TunnelInstance } from '../types'
|
|
7
|
-
import { binaryExists, spawnTunnelProcess, pollUntil } from '../utils'
|
|
8
|
-
|
|
9
|
-
export class NgrokTunnelProvider implements TunnelProvider {
|
|
10
|
-
id = 'ngrok'
|
|
11
|
-
name = 'ngrok'
|
|
12
|
-
supportsNamedTunnels = true
|
|
13
|
-
private process: ChildProcess | null = null
|
|
14
|
-
|
|
15
|
-
async isAvailable(): Promise<boolean> {
|
|
16
|
-
return binaryExists('ngrok')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async start(localPort: number, namedUrl?: string): Promise<TunnelInstance> {
|
|
20
|
-
const args = ['http', String(localPort)]
|
|
21
|
-
|
|
22
|
-
// Add subdomain if provided (requires ngrok account)
|
|
23
|
-
if (namedUrl) {
|
|
24
|
-
const subdomain = namedUrl.replace(/\.ngrok\.(io|app)$/, '')
|
|
25
|
-
args.push('--subdomain', subdomain)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
this.process = spawnTunnelProcess('ngrok', args)
|
|
29
|
-
|
|
30
|
-
// Poll ngrok's local API for tunnel URL
|
|
31
|
-
const url = await pollUntil(async () => {
|
|
32
|
-
try {
|
|
33
|
-
const response = await fetch('http://127.0.0.1:4040/api/tunnels')
|
|
34
|
-
if (!response.ok) return null
|
|
35
|
-
|
|
36
|
-
const data = await response.json() as { tunnels: Array<{ public_url: string }> }
|
|
37
|
-
const httpsTunnel = data.tunnels.find(t => t.public_url.startsWith('https://'))
|
|
38
|
-
return httpsTunnel?.public_url || null
|
|
39
|
-
} catch {
|
|
40
|
-
return null
|
|
41
|
-
}
|
|
42
|
-
}, 30000).catch(() => {
|
|
43
|
-
throw new Error('ngrok tunnel timeout')
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const proc = this.process
|
|
47
|
-
return {
|
|
48
|
-
providerId: this.id,
|
|
49
|
-
publicUrl: url,
|
|
50
|
-
stop: () => {
|
|
51
|
-
proc?.kill('SIGTERM')
|
|
52
|
-
this.process = null
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Tailscale Funnel Provider
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
|
-
import { spawn, ChildProcess } from 'child_process'
|
|
6
|
-
import type { TunnelProvider, TunnelInstance } from '../types'
|
|
7
|
-
import { binaryExists, spawnTunnelProcess, execJson } from '../utils'
|
|
8
|
-
|
|
9
|
-
export class TailscaleTunnelProvider implements TunnelProvider {
|
|
10
|
-
id = 'tailscale'
|
|
11
|
-
name = 'Tailscale Funnel'
|
|
12
|
-
supportsNamedTunnels = false
|
|
13
|
-
private process: ChildProcess | null = null
|
|
14
|
-
|
|
15
|
-
async isAvailable(): Promise<boolean> {
|
|
16
|
-
if (!await binaryExists('tailscale')) return false
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const status = await execJson<{ BackendState: string }>('tailscale status --json')
|
|
20
|
-
return status.BackendState === 'Running'
|
|
21
|
-
} catch {
|
|
22
|
-
return false
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async start(localPort: number): Promise<TunnelInstance> {
|
|
27
|
-
// Get hostname from Tailscale status
|
|
28
|
-
const status = await execJson<{ Self?: { DNSName?: string } }>('tailscale status --json')
|
|
29
|
-
const hostname = status.Self?.DNSName?.replace(/\.$/, '')
|
|
30
|
-
|
|
31
|
-
if (!hostname) throw new Error('Could not determine Tailscale hostname')
|
|
32
|
-
|
|
33
|
-
// Start funnel
|
|
34
|
-
this.process = spawnTunnelProcess('tailscale', ['funnel', String(localPort)])
|
|
35
|
-
|
|
36
|
-
// Wait for funnel to initialize
|
|
37
|
-
await new Promise(r => setTimeout(r, 2000))
|
|
38
|
-
|
|
39
|
-
const proc = this.process
|
|
40
|
-
return {
|
|
41
|
-
providerId: this.id,
|
|
42
|
-
publicUrl: `https://${hostname}`,
|
|
43
|
-
stop: () => {
|
|
44
|
-
spawn('tailscale', ['funnel', 'off'], { stdio: 'ignore' })
|
|
45
|
-
proc?.kill('SIGTERM')
|
|
46
|
-
this.process = null
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|