sub-bridge 1.1.2 → 1.2.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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/dist/tunnel/providers/index.d.ts +1 -0
- package/dist/tunnel/providers/index.d.ts.map +1 -1
- package/dist/tunnel/providers/index.js +3 -1
- package/dist/tunnel/providers/index.js.map +1 -1
- package/dist/tunnel/providers/manual.d.ts +11 -0
- package/dist/tunnel/providers/manual.d.ts.map +1 -0
- package/dist/tunnel/providers/manual.js +41 -0
- package/dist/tunnel/providers/manual.js.map +1 -0
- package/dist/tunnel/registry.d.ts.map +1 -1
- package/dist/tunnel/registry.js +9 -3
- package/dist/tunnel/registry.js.map +1 -1
- package/index.html +34 -4
- package/package.json +1 -1
- package/src/tunnel/providers/index.ts +1 -0
- package/src/tunnel/providers/manual.ts +46 -0
- package/src/tunnel/registry.ts +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.0](https://github.com/buremba/sub-bridge/compare/sub-bridge-v1.1.2...sub-bridge-v1.2.0) (2025-12-25)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add custom URL provider for users with their own public IP/domain ([ccc5719](https://github.com/buremba/sub-bridge/commit/ccc57191df3ddfaf567a257e50ecae12f4a63dbe))
|
|
9
|
+
|
|
3
10
|
## [1.1.2](https://github.com/buremba/sub-bridge/compare/sub-bridge-v1.1.1...sub-bridge-v1.1.2) (2025-12-25)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -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;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// Tunnel Providers Index
|
|
4
4
|
// ============================================================================
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.CloudflareTunnelProvider = void 0;
|
|
6
|
+
exports.ManualTunnelProvider = 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 manual_1 = require("./manual");
|
|
10
|
+
Object.defineProperty(exports, "ManualTunnelProvider", { enumerable: true, get: function () { return manual_1.ManualTunnelProvider; } });
|
|
9
11
|
//# 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;AACjC,mCAA+C;AAAtC,8GAAA,oBAAoB,OAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TunnelProvider, TunnelInstance, NamedTunnelInfo } from '../types';
|
|
2
|
+
export declare class ManualTunnelProvider implements TunnelProvider {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
supportsNamedTunnels: boolean;
|
|
6
|
+
isAvailable(): Promise<boolean>;
|
|
7
|
+
isAuthenticated(): Promise<boolean>;
|
|
8
|
+
listTunnels(): Promise<NamedTunnelInfo[]>;
|
|
9
|
+
start(localPort: number, customUrl?: string): Promise<TunnelInstance>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=manual.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual.d.ts","sourceRoot":"","sources":["../../../src/tunnel/providers/manual.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAE/E,qBAAa,oBAAqB,YAAW,cAAc;IACzD,EAAE,SAAW;IACb,IAAI,SAAe;IACnB,oBAAoB,UAAQ;IAEtB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAInC,WAAW,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAIzC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CAsB5E"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Manual/Custom URL Provider - User provides their own public URL
|
|
4
|
+
// ============================================================================
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ManualTunnelProvider = void 0;
|
|
7
|
+
class ManualTunnelProvider {
|
|
8
|
+
id = 'manual';
|
|
9
|
+
name = 'Custom URL';
|
|
10
|
+
supportsNamedTunnels = false;
|
|
11
|
+
async isAvailable() {
|
|
12
|
+
return true; // Always available - no dependencies
|
|
13
|
+
}
|
|
14
|
+
async isAuthenticated() {
|
|
15
|
+
return true; // No authentication needed
|
|
16
|
+
}
|
|
17
|
+
async listTunnels() {
|
|
18
|
+
return []; // No named tunnels for manual provider
|
|
19
|
+
}
|
|
20
|
+
async start(localPort, customUrl) {
|
|
21
|
+
if (!customUrl) {
|
|
22
|
+
throw new Error('Custom URL is required. Enter your public URL (e.g., https://api.mydomain.com)');
|
|
23
|
+
}
|
|
24
|
+
// Normalize URL - ensure it has a protocol
|
|
25
|
+
let publicUrl = customUrl.trim();
|
|
26
|
+
if (!publicUrl.startsWith('http://') && !publicUrl.startsWith('https://')) {
|
|
27
|
+
publicUrl = 'https://' + publicUrl;
|
|
28
|
+
}
|
|
29
|
+
// Remove trailing slash for consistency
|
|
30
|
+
publicUrl = publicUrl.replace(/\/$/, '');
|
|
31
|
+
return {
|
|
32
|
+
providerId: this.id,
|
|
33
|
+
publicUrl,
|
|
34
|
+
stop: () => {
|
|
35
|
+
// No-op - nothing to stop since no process was started
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.ManualTunnelProvider = ManualTunnelProvider;
|
|
41
|
+
//# sourceMappingURL=manual.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual.js","sourceRoot":"","sources":["../../../src/tunnel/providers/manual.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,kEAAkE;AAClE,+EAA+E;;;AAI/E,MAAa,oBAAoB;IAC/B,EAAE,GAAG,QAAQ,CAAA;IACb,IAAI,GAAG,YAAY,CAAA;IACnB,oBAAoB,GAAG,KAAK,CAAA;IAE5B,KAAK,CAAC,WAAW;QACf,OAAO,IAAI,CAAA,CAAC,qCAAqC;IACnD,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,OAAO,IAAI,CAAA,CAAC,2BAA2B;IACzC,CAAC;IAED,KAAK,CAAC,WAAW;QACf,OAAO,EAAE,CAAA,CAAC,uCAAuC;IACnD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB,EAAE,SAAkB;QAC/C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,gFAAgF,CAAC,CAAA;QACnG,CAAC;QAED,2CAA2C;QAC3C,IAAI,SAAS,GAAG,SAAS,CAAC,IAAI,EAAE,CAAA;QAChC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1E,SAAS,GAAG,UAAU,GAAG,SAAS,CAAA;QACpC,CAAC;QAED,wCAAwC;QACxC,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QAExC,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,EAAE;YACnB,SAAS;YACT,IAAI,EAAE,GAAG,EAAE;gBACT,uDAAuD;YACzD,CAAC;SACF,CAAA;IACH,CAAC;CACF;AAvCD,oDAuCC"}
|
|
@@ -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;AAmDzF,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,SAAS,CAAsB;;
|
|
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;;IAUjC,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;IAsD5F,IAAI,IAAI,YAAY;IASpB,YAAY,IAAI,MAAM,GAAG,IAAI;CAG9B"}
|
package/dist/tunnel/registry.js
CHANGED
|
@@ -58,8 +58,10 @@ class TunnelRegistry {
|
|
|
58
58
|
startedAt = null;
|
|
59
59
|
lastError = null;
|
|
60
60
|
constructor() {
|
|
61
|
-
const
|
|
62
|
-
this.providers.set(
|
|
61
|
+
const cloudflare = new providers_1.CloudflareTunnelProvider();
|
|
62
|
+
this.providers.set(cloudflare.id, cloudflare);
|
|
63
|
+
const manual = new providers_1.ManualTunnelProvider();
|
|
64
|
+
this.providers.set(manual.id, manual);
|
|
63
65
|
}
|
|
64
66
|
async getProviders() {
|
|
65
67
|
const results = [];
|
|
@@ -104,6 +106,7 @@ class TunnelRegistry {
|
|
|
104
106
|
throw new Error(`Tunnel provider ${provider.name} is not available`);
|
|
105
107
|
}
|
|
106
108
|
const isQuickCloudflare = providerId === 'cloudflare' && !namedUrl;
|
|
109
|
+
const isManual = providerId === 'manual';
|
|
107
110
|
const maxAttempts = isQuickCloudflare ? 4 : 1;
|
|
108
111
|
let lastError = null;
|
|
109
112
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -111,7 +114,10 @@ class TunnelRegistry {
|
|
|
111
114
|
this.lastError = null;
|
|
112
115
|
this.activeTunnel = await provider.start(localPort, namedUrl);
|
|
113
116
|
this.startedAt = new Date().toISOString();
|
|
114
|
-
|
|
117
|
+
// Skip health check for manual provider - user trusts their own URL
|
|
118
|
+
if (!isManual) {
|
|
119
|
+
await verifyTunnelHealth(this.activeTunnel.publicUrl, localPort);
|
|
120
|
+
}
|
|
115
121
|
return this.getStatus();
|
|
116
122
|
}
|
|
117
123
|
catch (error) {
|
|
@@ -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/index.html
CHANGED
|
@@ -255,8 +255,37 @@
|
|
|
255
255
|
|
|
256
256
|
<template x-if="!hasTunnel">
|
|
257
257
|
<div class="space-y-3">
|
|
258
|
-
<!--
|
|
259
|
-
<template x-if="
|
|
258
|
+
<!-- Provider selector -->
|
|
259
|
+
<template x-if="tunnel.providers.length > 1">
|
|
260
|
+
<div class="space-y-2">
|
|
261
|
+
<label class="text-[10px] font-semibold uppercase tracking-wider text-neutral-500">Provider</label>
|
|
262
|
+
<select
|
|
263
|
+
x-model="tunnel.selectedProvider"
|
|
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
|
+
<template x-for="p in tunnel.providers.filter(p => p.available)" :key="p.id">
|
|
267
|
+
<option :value="p.id" x-text="p.name"></option>
|
|
268
|
+
</template>
|
|
269
|
+
</select>
|
|
270
|
+
</div>
|
|
271
|
+
</template>
|
|
272
|
+
|
|
273
|
+
<!-- Custom URL input (for manual provider) -->
|
|
274
|
+
<template x-if="tunnel.selectedProvider === 'manual'">
|
|
275
|
+
<div class="space-y-2">
|
|
276
|
+
<label class="text-[10px] font-semibold uppercase tracking-wider text-neutral-500">Your Public URL</label>
|
|
277
|
+
<input
|
|
278
|
+
type="text"
|
|
279
|
+
x-model="tunnel.customDomain"
|
|
280
|
+
placeholder="https://your-domain.com or http://your-ip:port"
|
|
281
|
+
class="w-full rounded-lg border border-neutral-800 bg-neutral-950 px-3 py-2 text-sm text-neutral-100 placeholder:text-neutral-500 focus:border-neutral-600 focus:outline-none"
|
|
282
|
+
>
|
|
283
|
+
<p class="text-[10px] text-neutral-500">Enter the public URL where this server is accessible (via reverse proxy, port forwarding, etc.)</p>
|
|
284
|
+
</div>
|
|
285
|
+
</template>
|
|
286
|
+
|
|
287
|
+
<!-- Named tunnel selector (only for Cloudflare when authenticated) -->
|
|
288
|
+
<template x-if="tunnel.selectedProvider === 'cloudflare' && selectedProvider?.authenticated && selectedProvider?.namedTunnels?.length">
|
|
260
289
|
<div class="space-y-2">
|
|
261
290
|
<label class="text-[10px] font-semibold uppercase tracking-wider text-neutral-500">Use Named Tunnel (optional)</label>
|
|
262
291
|
<select
|
|
@@ -270,6 +299,7 @@
|
|
|
270
299
|
</select>
|
|
271
300
|
</div>
|
|
272
301
|
</template>
|
|
302
|
+
|
|
273
303
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
274
304
|
<div class="flex flex-wrap gap-2" x-show="tunnel.loading">
|
|
275
305
|
<span class="h-7 w-20 rounded-lg border border-neutral-800 bg-neutral-900/60 animate-pulse"></span>
|
|
@@ -277,9 +307,9 @@
|
|
|
277
307
|
<template x-if="!tunnel.status.active && !hasExternalUrl">
|
|
278
308
|
<button
|
|
279
309
|
@click="startTunnel()"
|
|
280
|
-
:disabled="tunnel.starting || !tunnel.selectedProvider || !tunnel.providers.some(p => p.available)"
|
|
310
|
+
:disabled="tunnel.starting || !tunnel.selectedProvider || !tunnel.providers.some(p => p.available) || (tunnel.selectedProvider === 'manual' && !tunnel.customDomain)"
|
|
281
311
|
class="rounded-lg bg-neutral-100 px-3 py-2 text-xs font-semibold text-neutral-950 hover:bg-white disabled:opacity-50"
|
|
282
|
-
x-text="tunnel.starting ? 'Creating...' : 'Create Tunnel'"
|
|
312
|
+
x-text="tunnel.starting ? (tunnel.selectedProvider === 'manual' ? 'Setting...' : 'Creating...') : (tunnel.selectedProvider === 'manual' ? 'Set URL' : 'Create Tunnel')"
|
|
283
313
|
x-show="!tunnel.loading"
|
|
284
314
|
></button>
|
|
285
315
|
</template>
|
package/package.json
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Manual/Custom URL Provider - User provides their own public URL
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import type { TunnelProvider, TunnelInstance, NamedTunnelInfo } from '../types'
|
|
6
|
+
|
|
7
|
+
export class ManualTunnelProvider implements TunnelProvider {
|
|
8
|
+
id = 'manual'
|
|
9
|
+
name = 'Custom URL'
|
|
10
|
+
supportsNamedTunnels = false
|
|
11
|
+
|
|
12
|
+
async isAvailable(): Promise<boolean> {
|
|
13
|
+
return true // Always available - no dependencies
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async isAuthenticated(): Promise<boolean> {
|
|
17
|
+
return true // No authentication needed
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async listTunnels(): Promise<NamedTunnelInfo[]> {
|
|
21
|
+
return [] // No named tunnels for manual provider
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async start(localPort: number, customUrl?: string): Promise<TunnelInstance> {
|
|
25
|
+
if (!customUrl) {
|
|
26
|
+
throw new Error('Custom URL is required. Enter your public URL (e.g., https://api.mydomain.com)')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Normalize URL - ensure it has a protocol
|
|
30
|
+
let publicUrl = customUrl.trim()
|
|
31
|
+
if (!publicUrl.startsWith('http://') && !publicUrl.startsWith('https://')) {
|
|
32
|
+
publicUrl = 'https://' + publicUrl
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Remove trailing slash for consistency
|
|
36
|
+
publicUrl = publicUrl.replace(/\/$/, '')
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
providerId: this.id,
|
|
40
|
+
publicUrl,
|
|
41
|
+
stop: () => {
|
|
42
|
+
// No-op - nothing to stop since no process was started
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/tunnel/registry.ts
CHANGED
|
@@ -51,7 +51,7 @@ async function verifyTunnelHealth(publicUrl: string, expectedPort: number): Prom
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
import { CloudflareTunnelProvider } from './providers'
|
|
54
|
+
import { CloudflareTunnelProvider, ManualTunnelProvider } from './providers'
|
|
55
55
|
|
|
56
56
|
export class TunnelRegistry {
|
|
57
57
|
private providers: Map<string, TunnelProvider> = new Map()
|
|
@@ -60,8 +60,11 @@ export class TunnelRegistry {
|
|
|
60
60
|
private lastError: string | null = null
|
|
61
61
|
|
|
62
62
|
constructor() {
|
|
63
|
-
const
|
|
64
|
-
this.providers.set(
|
|
63
|
+
const cloudflare = new CloudflareTunnelProvider()
|
|
64
|
+
this.providers.set(cloudflare.id, cloudflare)
|
|
65
|
+
|
|
66
|
+
const manual = new ManualTunnelProvider()
|
|
67
|
+
this.providers.set(manual.id, manual)
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
async getProviders(): Promise<ProviderInfo[]> {
|
|
@@ -112,6 +115,7 @@ export class TunnelRegistry {
|
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
const isQuickCloudflare = providerId === 'cloudflare' && !namedUrl
|
|
118
|
+
const isManual = providerId === 'manual'
|
|
115
119
|
const maxAttempts = isQuickCloudflare ? 4 : 1
|
|
116
120
|
let lastError: Error | null = null
|
|
117
121
|
|
|
@@ -120,7 +124,10 @@ export class TunnelRegistry {
|
|
|
120
124
|
this.lastError = null
|
|
121
125
|
this.activeTunnel = await provider.start(localPort, namedUrl)
|
|
122
126
|
this.startedAt = new Date().toISOString()
|
|
123
|
-
|
|
127
|
+
// Skip health check for manual provider - user trusts their own URL
|
|
128
|
+
if (!isManual) {
|
|
129
|
+
await verifyTunnelHealth(this.activeTunnel.publicUrl, localPort)
|
|
130
|
+
}
|
|
124
131
|
return this.getStatus()
|
|
125
132
|
} catch (error) {
|
|
126
133
|
lastError = error instanceof Error ? error : new Error(String(error))
|