instar 1.2.34 → 1.2.35
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/CloudflareNamedProvider.d.ts +51 -0
- package/dist/tunnel/CloudflareNamedProvider.d.ts.map +1 -0
- package/dist/tunnel/CloudflareNamedProvider.js +258 -0
- package/dist/tunnel/CloudflareNamedProvider.js.map +1 -0
- package/dist/tunnel/CloudflareQuickProvider.d.ts +48 -0
- package/dist/tunnel/CloudflareQuickProvider.d.ts.map +1 -0
- package/dist/tunnel/CloudflareQuickProvider.js +201 -0
- package/dist/tunnel/CloudflareQuickProvider.js.map +1 -0
- package/dist/tunnel/TunnelLifecycle.d.ts +190 -0
- package/dist/tunnel/TunnelLifecycle.d.ts.map +1 -0
- package/dist/tunnel/TunnelLifecycle.js +222 -0
- package/dist/tunnel/TunnelLifecycle.js.map +1 -0
- package/dist/tunnel/TunnelNotifier.d.ts +110 -0
- package/dist/tunnel/TunnelNotifier.d.ts.map +1 -0
- package/dist/tunnel/TunnelNotifier.js +268 -0
- package/dist/tunnel/TunnelNotifier.js.map +1 -0
- package/dist/tunnel/TunnelProvider.d.ts +80 -0
- package/dist/tunnel/TunnelProvider.d.ts.map +1 -0
- package/dist/tunnel/TunnelProvider.js +28 -0
- package/dist/tunnel/TunnelProvider.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.2.35.md +96 -0
- package/upgrades/side-effects/tunnel-failure-resilience.md +118 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudflareNamedProvider — Tier-1 persistent Cloudflare named tunnel.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the original `TunnelManager.startNamedTunnel()` and
|
|
5
|
+
* `startConfigFileTunnel()` so the manager can drive multiple providers
|
|
6
|
+
* through `TunnelProvider`. The named path supports two auth modes,
|
|
7
|
+
* preserved verbatim from the original behavior:
|
|
8
|
+
*
|
|
9
|
+
* - Token auth: `cloudflared` via `Tunnel.withToken(token)`. The URL
|
|
10
|
+
* is the configured hostname (passed via `hostname`); cloudflared
|
|
11
|
+
* surfaces 'connected' rather than 'url'.
|
|
12
|
+
* - Config-file auth: spawn `cloudflared tunnel --config <file> run`
|
|
13
|
+
* directly. URL is the configured hostname; we watch stderr for
|
|
14
|
+
* "Registered tunnel connection" / "Connection registered" to know
|
|
15
|
+
* the tunnel is up.
|
|
16
|
+
*
|
|
17
|
+
* `isAvailable()` returns false when neither token NOR configFile is
|
|
18
|
+
* configured, so the manager skips this provider on quick-only installs.
|
|
19
|
+
*
|
|
20
|
+
* Like the quick provider, this module owns ONLY spawn + URL emission
|
|
21
|
+
* + teardown. Retry, reconnect, fallback, and notification are owned by
|
|
22
|
+
* the manager per the spec's single-owner mandate.
|
|
23
|
+
*/
|
|
24
|
+
import type { TunnelProvider, TunnelProviderHandle, ProviderName, ProviderTier } from './TunnelProvider.js';
|
|
25
|
+
export interface CloudflareNamedProviderOptions {
|
|
26
|
+
/** Cloudflare tunnel token (token-auth mode). */
|
|
27
|
+
token?: string;
|
|
28
|
+
/** Config file path (config-file-auth mode). */
|
|
29
|
+
configFile?: string;
|
|
30
|
+
/** Public hostname for the tunnel (e.g. echo.dawn-tunnel.dev). */
|
|
31
|
+
hostname?: string;
|
|
32
|
+
/** Start timeout in milliseconds (default: 30_000 for token; 15_000 for config-file). */
|
|
33
|
+
startTimeoutMs?: number;
|
|
34
|
+
}
|
|
35
|
+
export declare class CloudflareNamedProvider implements TunnelProvider {
|
|
36
|
+
readonly name: ProviderName;
|
|
37
|
+
readonly tier: ProviderTier;
|
|
38
|
+
private readonly token?;
|
|
39
|
+
private readonly configFile?;
|
|
40
|
+
private readonly hostname?;
|
|
41
|
+
private readonly startTimeoutMs?;
|
|
42
|
+
constructor(opts: CloudflareNamedProviderOptions);
|
|
43
|
+
isAvailable(): Promise<boolean>;
|
|
44
|
+
start(localPort: number): Promise<TunnelProviderHandle>;
|
|
45
|
+
private startWithToken;
|
|
46
|
+
private startConfigFile;
|
|
47
|
+
private stopHandle;
|
|
48
|
+
private stopChild;
|
|
49
|
+
private killWithEscalation;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=CloudflareNamedProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloudflareNamedProvider.d.ts","sourceRoot":"","sources":["../../src/tunnel/CloudflareNamedProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACb,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,8BAA8B;IAC7C,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,uBAAwB,YAAW,cAAc;IAC5D,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAsB;IACjD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAK;IAEhC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAS;gBAE7B,IAAI,EAAE,8BAA8B;IAO1C,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAMrC,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;YAczC,cAAc;IAuE5B,OAAO,CAAC,eAAe;YAuET,UAAU;YAQV,SAAS;YAOT,kBAAkB;CAwBjC"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudflareNamedProvider — Tier-1 persistent Cloudflare named tunnel.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the original `TunnelManager.startNamedTunnel()` and
|
|
5
|
+
* `startConfigFileTunnel()` so the manager can drive multiple providers
|
|
6
|
+
* through `TunnelProvider`. The named path supports two auth modes,
|
|
7
|
+
* preserved verbatim from the original behavior:
|
|
8
|
+
*
|
|
9
|
+
* - Token auth: `cloudflared` via `Tunnel.withToken(token)`. The URL
|
|
10
|
+
* is the configured hostname (passed via `hostname`); cloudflared
|
|
11
|
+
* surfaces 'connected' rather than 'url'.
|
|
12
|
+
* - Config-file auth: spawn `cloudflared tunnel --config <file> run`
|
|
13
|
+
* directly. URL is the configured hostname; we watch stderr for
|
|
14
|
+
* "Registered tunnel connection" / "Connection registered" to know
|
|
15
|
+
* the tunnel is up.
|
|
16
|
+
*
|
|
17
|
+
* `isAvailable()` returns false when neither token NOR configFile is
|
|
18
|
+
* configured, so the manager skips this provider on quick-only installs.
|
|
19
|
+
*
|
|
20
|
+
* Like the quick provider, this module owns ONLY spawn + URL emission
|
|
21
|
+
* + teardown. Retry, reconnect, fallback, and notification are owned by
|
|
22
|
+
* the manager per the spec's single-owner mandate.
|
|
23
|
+
*/
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import { spawn } from 'node:child_process';
|
|
26
|
+
import { bin, install, Tunnel } from 'cloudflared';
|
|
27
|
+
export class CloudflareNamedProvider {
|
|
28
|
+
name = 'cloudflare-named';
|
|
29
|
+
tier = 1;
|
|
30
|
+
token;
|
|
31
|
+
configFile;
|
|
32
|
+
hostname;
|
|
33
|
+
startTimeoutMs;
|
|
34
|
+
constructor(opts) {
|
|
35
|
+
this.token = opts.token;
|
|
36
|
+
this.configFile = opts.configFile;
|
|
37
|
+
this.hostname = opts.hostname;
|
|
38
|
+
this.startTimeoutMs = opts.startTimeoutMs;
|
|
39
|
+
}
|
|
40
|
+
async isAvailable() {
|
|
41
|
+
if (!this.token && !this.configFile)
|
|
42
|
+
return false;
|
|
43
|
+
if (this.configFile && !fs.existsSync(this.configFile))
|
|
44
|
+
return false;
|
|
45
|
+
return typeof bin === 'string' && bin.length > 0;
|
|
46
|
+
}
|
|
47
|
+
start(localPort) {
|
|
48
|
+
// Named tunnel doesn't actually use localPort — the hostname is
|
|
49
|
+
// pre-configured in Cloudflare to map to the agent's local port.
|
|
50
|
+
// Accepting the parameter keeps the TunnelProvider signature uniform.
|
|
51
|
+
void localPort;
|
|
52
|
+
if (this.configFile) {
|
|
53
|
+
return this.startConfigFile();
|
|
54
|
+
}
|
|
55
|
+
if (this.token) {
|
|
56
|
+
return this.startWithToken();
|
|
57
|
+
}
|
|
58
|
+
return Promise.reject(new Error('binary-missing: named tunnel requires either a token or a configFile'));
|
|
59
|
+
}
|
|
60
|
+
async startWithToken() {
|
|
61
|
+
if (!fs.existsSync(bin)) {
|
|
62
|
+
try {
|
|
63
|
+
await install(bin);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
throw new Error(`binary-missing: cloudflared install failed: ${err.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
let tunnel;
|
|
71
|
+
try {
|
|
72
|
+
tunnel = Tunnel.withToken(this.token);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
reject(new Error(`process-exit: failed to spawn cloudflared (token): ${err.message}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const hostname = this.hostname;
|
|
79
|
+
const timeoutMs = this.startTimeoutMs ?? 30_000;
|
|
80
|
+
let resolved = false;
|
|
81
|
+
const timeout = setTimeout(() => {
|
|
82
|
+
if (resolved)
|
|
83
|
+
return;
|
|
84
|
+
resolved = true;
|
|
85
|
+
try {
|
|
86
|
+
tunnel.stop();
|
|
87
|
+
}
|
|
88
|
+
catch { /* may already be dead */ }
|
|
89
|
+
reject(new Error('timeout: cloudflared named tunnel did not connect within the start budget'));
|
|
90
|
+
}, timeoutMs);
|
|
91
|
+
const finalize = (url) => {
|
|
92
|
+
resolved = true;
|
|
93
|
+
clearTimeout(timeout);
|
|
94
|
+
resolve({
|
|
95
|
+
url,
|
|
96
|
+
stop: () => this.stopHandle(tunnel),
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
tunnel.once('url', (url) => {
|
|
100
|
+
if (resolved)
|
|
101
|
+
return;
|
|
102
|
+
finalize(url);
|
|
103
|
+
});
|
|
104
|
+
tunnel.once('connected', () => {
|
|
105
|
+
// For token-auth named tunnels, cloudflared often surfaces
|
|
106
|
+
// 'connected' before/instead of 'url'; the public URL is the
|
|
107
|
+
// pre-configured hostname.
|
|
108
|
+
if (resolved)
|
|
109
|
+
return;
|
|
110
|
+
if (hostname) {
|
|
111
|
+
finalize(`https://${hostname}`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
tunnel.on('error', (err) => {
|
|
115
|
+
if (resolved)
|
|
116
|
+
return;
|
|
117
|
+
resolved = true;
|
|
118
|
+
clearTimeout(timeout);
|
|
119
|
+
const msg = (err?.message ?? '').toLowerCase();
|
|
120
|
+
if (msg.includes('429') || msg.includes('1015') || msg.includes('rate limit')) {
|
|
121
|
+
reject(new Error(`rate-limited: cloudflared named tunnel rate-limited: ${err.message}`));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
reject(err);
|
|
125
|
+
});
|
|
126
|
+
tunnel.on('exit', (code) => {
|
|
127
|
+
if (resolved)
|
|
128
|
+
return;
|
|
129
|
+
resolved = true;
|
|
130
|
+
clearTimeout(timeout);
|
|
131
|
+
reject(new Error(`process-exit code ${code}: named tunnel exited before URL emit`));
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
startConfigFile() {
|
|
136
|
+
const configFile = this.configFile;
|
|
137
|
+
const hostname = this.hostname;
|
|
138
|
+
const timeoutMs = this.startTimeoutMs ?? 15_000;
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
if (!fs.existsSync(configFile)) {
|
|
141
|
+
reject(new Error(`binary-missing: tunnel config file not found: ${configFile}`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (!fs.existsSync(bin)) {
|
|
145
|
+
// Spawn will fail; surface as binary-missing for consistency.
|
|
146
|
+
reject(new Error(`binary-missing: cloudflared binary not installed: ${bin}`));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const child = spawn(bin, ['tunnel', '--config', configFile, 'run'], {
|
|
150
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
151
|
+
});
|
|
152
|
+
let resolved = false;
|
|
153
|
+
let stderrBuffer = '';
|
|
154
|
+
const timeout = setTimeout(() => {
|
|
155
|
+
if (resolved)
|
|
156
|
+
return;
|
|
157
|
+
resolved = true;
|
|
158
|
+
// Config-file tunnels sometimes never log a 'connection registered'
|
|
159
|
+
// line but still come up. If hostname is configured, accept it as
|
|
160
|
+
// the URL — same behavior as the legacy implementation.
|
|
161
|
+
if (hostname) {
|
|
162
|
+
resolve({
|
|
163
|
+
url: `https://${hostname}`,
|
|
164
|
+
stop: () => this.stopChild(child),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
try {
|
|
169
|
+
child.kill('SIGTERM');
|
|
170
|
+
}
|
|
171
|
+
catch { /* already dead */ }
|
|
172
|
+
reject(new Error('timeout: named config-file tunnel timed out and no hostname configured'));
|
|
173
|
+
}
|
|
174
|
+
}, timeoutMs);
|
|
175
|
+
child.stderr.on('data', (data) => {
|
|
176
|
+
const line = data.toString();
|
|
177
|
+
stderrBuffer = (stderrBuffer + line).slice(-500);
|
|
178
|
+
if (resolved)
|
|
179
|
+
return;
|
|
180
|
+
if (line.includes('Registered tunnel connection') || line.includes('Connection registered')) {
|
|
181
|
+
if (hostname) {
|
|
182
|
+
resolved = true;
|
|
183
|
+
clearTimeout(timeout);
|
|
184
|
+
resolve({
|
|
185
|
+
url: `https://${hostname}`,
|
|
186
|
+
stop: () => this.stopChild(child),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
child.on('error', (err) => {
|
|
192
|
+
if (resolved)
|
|
193
|
+
return;
|
|
194
|
+
resolved = true;
|
|
195
|
+
clearTimeout(timeout);
|
|
196
|
+
reject(new Error(`process-exit: failed to start config-file tunnel: ${err.message}`));
|
|
197
|
+
});
|
|
198
|
+
child.on('exit', (code) => {
|
|
199
|
+
if (resolved)
|
|
200
|
+
return;
|
|
201
|
+
resolved = true;
|
|
202
|
+
clearTimeout(timeout);
|
|
203
|
+
reject(new Error(`process-exit code ${code}: config-file tunnel exited before connection: ${stderrBuffer}`));
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async stopHandle(tunnel) {
|
|
208
|
+
const proc = tunnel.process;
|
|
209
|
+
const pid = proc?.pid;
|
|
210
|
+
try {
|
|
211
|
+
tunnel.stop();
|
|
212
|
+
}
|
|
213
|
+
catch { /* already dead */ }
|
|
214
|
+
if (!pid)
|
|
215
|
+
return;
|
|
216
|
+
await this.killWithEscalation(pid);
|
|
217
|
+
}
|
|
218
|
+
async stopChild(child) {
|
|
219
|
+
const pid = child.pid;
|
|
220
|
+
try {
|
|
221
|
+
child.kill('SIGTERM');
|
|
222
|
+
}
|
|
223
|
+
catch { /* already dead */ }
|
|
224
|
+
if (!pid)
|
|
225
|
+
return;
|
|
226
|
+
await this.killWithEscalation(pid);
|
|
227
|
+
}
|
|
228
|
+
async killWithEscalation(pid, timeoutMs = 5_000) {
|
|
229
|
+
const exited = await new Promise((resolve) => {
|
|
230
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
231
|
+
try {
|
|
232
|
+
process.kill(pid, 0);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
clearTimeout(timer);
|
|
236
|
+
resolve(true);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const poll = setInterval(() => {
|
|
240
|
+
try {
|
|
241
|
+
process.kill(pid, 0);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
clearInterval(poll);
|
|
245
|
+
clearTimeout(timer);
|
|
246
|
+
resolve(true);
|
|
247
|
+
}
|
|
248
|
+
}, 200);
|
|
249
|
+
});
|
|
250
|
+
if (!exited) {
|
|
251
|
+
try {
|
|
252
|
+
process.kill(pid, 'SIGKILL');
|
|
253
|
+
}
|
|
254
|
+
catch { /* already dead */ }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=CloudflareNamedProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloudflareNamedProvider.js","sourceRoot":"","sources":["../../src/tunnel/CloudflareNamedProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudflareQuickProvider — Tier-1 zero-config Cloudflare quick tunnel.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the original `TunnelManager.startQuickTunnel()` so the
|
|
5
|
+
* manager can drive multiple providers through the common
|
|
6
|
+
* `TunnelProvider` interface. Behavior preserved verbatim where it
|
|
7
|
+
* doesn't conflict with the spec's single-owner mandate:
|
|
8
|
+
*
|
|
9
|
+
* - Owns: spawning `cloudflared` with --config-isolation; resolving
|
|
10
|
+
* when the quick-tunnel URL emits; tearing down on stop().
|
|
11
|
+
* - Does NOT own: retry/backoff, reconnect-on-disconnect, failure
|
|
12
|
+
* notification, episode/consent state. Those are the manager's
|
|
13
|
+
* responsibility now (per
|
|
14
|
+
* specs/dev-infrastructure/tunnel-failure-resilience.md Part 1's
|
|
15
|
+
* "single-owner mandate" that retires the old in-provider reconnect
|
|
16
|
+
* loop).
|
|
17
|
+
*
|
|
18
|
+
* Failure-mode classification is surfaced via the rejection Error's
|
|
19
|
+
* `message`; the manager parses to `ProviderFailureReason`. This module
|
|
20
|
+
* uses fixed substrings the manager recognizes:
|
|
21
|
+
* - "rate-limited" (Cloudflare 429 / 1015 detected from cloudflared stderr)
|
|
22
|
+
* - "binary-missing" (the bundled cloudflared binary path doesn't exist)
|
|
23
|
+
* - "timeout" (URL didn't emit within the start budget)
|
|
24
|
+
* - "process-exit code N" (child exited before URL emit)
|
|
25
|
+
*/
|
|
26
|
+
import type { TunnelProvider, TunnelProviderHandle, ProviderName, ProviderTier } from './TunnelProvider.js';
|
|
27
|
+
export interface CloudflareQuickProviderOptions {
|
|
28
|
+
/** Local port to expose. */
|
|
29
|
+
port: number;
|
|
30
|
+
/** State directory for the quick-tunnel isolation config file. */
|
|
31
|
+
stateDir: string;
|
|
32
|
+
/** Start timeout in milliseconds (default: 30_000). */
|
|
33
|
+
startTimeoutMs?: number;
|
|
34
|
+
}
|
|
35
|
+
export declare class CloudflareQuickProvider implements TunnelProvider {
|
|
36
|
+
readonly name: ProviderName;
|
|
37
|
+
readonly tier: ProviderTier;
|
|
38
|
+
private readonly port;
|
|
39
|
+
private readonly stateDir;
|
|
40
|
+
private readonly startTimeoutMs;
|
|
41
|
+
constructor(opts: CloudflareQuickProviderOptions);
|
|
42
|
+
isAvailable(): Promise<boolean>;
|
|
43
|
+
start(localPort: number): Promise<TunnelProviderHandle>;
|
|
44
|
+
private startInner;
|
|
45
|
+
private classifyError;
|
|
46
|
+
private stopHandle;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=CloudflareQuickProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloudflareQuickProvider.d.ts","sourceRoot":"","sources":["../../src/tunnel/CloudflareQuickProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACb,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,8BAA8B;IAC7C,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,uBAAwB,YAAW,cAAc;IAC5D,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAsB;IACjD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAK;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,IAAI,EAAE,8BAA8B;IAM1C,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAQrC,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;YASzC,UAAU;IA6FxB,OAAO,CAAC,aAAa;YAiBP,UAAU;CAmCzB"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudflareQuickProvider — Tier-1 zero-config Cloudflare quick tunnel.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the original `TunnelManager.startQuickTunnel()` so the
|
|
5
|
+
* manager can drive multiple providers through the common
|
|
6
|
+
* `TunnelProvider` interface. Behavior preserved verbatim where it
|
|
7
|
+
* doesn't conflict with the spec's single-owner mandate:
|
|
8
|
+
*
|
|
9
|
+
* - Owns: spawning `cloudflared` with --config-isolation; resolving
|
|
10
|
+
* when the quick-tunnel URL emits; tearing down on stop().
|
|
11
|
+
* - Does NOT own: retry/backoff, reconnect-on-disconnect, failure
|
|
12
|
+
* notification, episode/consent state. Those are the manager's
|
|
13
|
+
* responsibility now (per
|
|
14
|
+
* specs/dev-infrastructure/tunnel-failure-resilience.md Part 1's
|
|
15
|
+
* "single-owner mandate" that retires the old in-provider reconnect
|
|
16
|
+
* loop).
|
|
17
|
+
*
|
|
18
|
+
* Failure-mode classification is surfaced via the rejection Error's
|
|
19
|
+
* `message`; the manager parses to `ProviderFailureReason`. This module
|
|
20
|
+
* uses fixed substrings the manager recognizes:
|
|
21
|
+
* - "rate-limited" (Cloudflare 429 / 1015 detected from cloudflared stderr)
|
|
22
|
+
* - "binary-missing" (the bundled cloudflared binary path doesn't exist)
|
|
23
|
+
* - "timeout" (URL didn't emit within the start budget)
|
|
24
|
+
* - "process-exit code N" (child exited before URL emit)
|
|
25
|
+
*/
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import { bin, install, Tunnel } from 'cloudflared';
|
|
29
|
+
export class CloudflareQuickProvider {
|
|
30
|
+
name = 'cloudflare-quick';
|
|
31
|
+
tier = 1;
|
|
32
|
+
port;
|
|
33
|
+
stateDir;
|
|
34
|
+
startTimeoutMs;
|
|
35
|
+
constructor(opts) {
|
|
36
|
+
this.port = opts.port;
|
|
37
|
+
this.stateDir = opts.stateDir;
|
|
38
|
+
this.startTimeoutMs = opts.startTimeoutMs ?? 30_000;
|
|
39
|
+
}
|
|
40
|
+
async isAvailable() {
|
|
41
|
+
// The cloudflared npm package installs the binary on-demand via
|
|
42
|
+
// install(bin). availability == we can install or already have it.
|
|
43
|
+
// We don't actually do the install here; we just confirm the path
|
|
44
|
+
// is resolvable. Install happens during start().
|
|
45
|
+
return typeof bin === 'string' && bin.length > 0;
|
|
46
|
+
}
|
|
47
|
+
start(localPort) {
|
|
48
|
+
// localPort overrides constructor-time port for flexibility, but we
|
|
49
|
+
// default to the constructor value.
|
|
50
|
+
const port = localPort || this.port;
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
void this.startInner(port, resolve, reject);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async startInner(port, resolve, reject) {
|
|
56
|
+
try {
|
|
57
|
+
if (!fs.existsSync(bin)) {
|
|
58
|
+
try {
|
|
59
|
+
await install(bin);
|
|
60
|
+
}
|
|
61
|
+
catch (installErr) {
|
|
62
|
+
reject(new Error(`binary-missing: cloudflared install failed: ${installErr.message}`));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
reject(new Error('binary-missing: cloudflared binary path unresolvable'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const localUrl = `http://127.0.0.1:${port}`;
|
|
72
|
+
let tunnel;
|
|
73
|
+
try {
|
|
74
|
+
// Empty config to prevent cloudflared from reading
|
|
75
|
+
// ~/.cloudflared/config.yml, whose named-tunnel ingress rules
|
|
76
|
+
// would override the quick-tunnel's --url proxy. Preserved from
|
|
77
|
+
// the original implementation.
|
|
78
|
+
const emptyConfig = path.join(this.stateDir, 'cloudflared-quick.yml');
|
|
79
|
+
const dir = path.dirname(emptyConfig);
|
|
80
|
+
if (!fs.existsSync(dir)) {
|
|
81
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
fs.writeFileSync(emptyConfig, '# Quick tunnel — no ingress rules\n');
|
|
84
|
+
tunnel = Tunnel.quick(localUrl, { '--config': emptyConfig });
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
reject(new Error(`process-exit: failed to spawn cloudflared: ${err.message}`));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
let resolved = false;
|
|
91
|
+
let urlEmitted = null;
|
|
92
|
+
let stderrTail = '';
|
|
93
|
+
const timeout = setTimeout(() => {
|
|
94
|
+
if (!resolved) {
|
|
95
|
+
resolved = true;
|
|
96
|
+
try {
|
|
97
|
+
tunnel.stop();
|
|
98
|
+
}
|
|
99
|
+
catch { /* may already be dead */ }
|
|
100
|
+
reject(new Error('timeout: cloudflared quick tunnel did not emit a URL within the start budget'));
|
|
101
|
+
}
|
|
102
|
+
}, this.startTimeoutMs);
|
|
103
|
+
tunnel.once('url', (url) => {
|
|
104
|
+
if (resolved)
|
|
105
|
+
return;
|
|
106
|
+
resolved = true;
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
urlEmitted = url;
|
|
109
|
+
resolve({
|
|
110
|
+
url,
|
|
111
|
+
stop: () => this.stopHandle(tunnel),
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
tunnel.on('error', (err) => {
|
|
115
|
+
// Capture stderr-ish context. The cloudflared npm wrapper emits
|
|
116
|
+
// 'error' for child exit failures; we use the message as is.
|
|
117
|
+
stderrTail = (stderrTail + ' ' + (err?.message ?? '')).slice(-500);
|
|
118
|
+
if (!resolved) {
|
|
119
|
+
resolved = true;
|
|
120
|
+
clearTimeout(timeout);
|
|
121
|
+
reject(this.classifyError(err?.message ?? '', stderrTail));
|
|
122
|
+
}
|
|
123
|
+
// After resolution, we do NOT auto-reconnect. The manager owns
|
|
124
|
+
// disconnect handling per the single-owner mandate.
|
|
125
|
+
});
|
|
126
|
+
tunnel.on('exit', (code) => {
|
|
127
|
+
if (!resolved) {
|
|
128
|
+
resolved = true;
|
|
129
|
+
clearTimeout(timeout);
|
|
130
|
+
const msg = code === 0
|
|
131
|
+
? 'process-exit: cloudflared exited cleanly before URL emission'
|
|
132
|
+
: `process-exit code ${code}: ${stderrTail || 'no stderr captured'}`;
|
|
133
|
+
reject(this.classifyError(msg, stderrTail));
|
|
134
|
+
}
|
|
135
|
+
// Post-resolution exits are surfaced to the manager via the
|
|
136
|
+
// handle's lifecycle. (The handle itself doesn't reconnect.)
|
|
137
|
+
});
|
|
138
|
+
// urlEmitted is referenced only inside the closure above; this
|
|
139
|
+
// suppresses an unused-variable lint while documenting intent.
|
|
140
|
+
void urlEmitted;
|
|
141
|
+
}
|
|
142
|
+
classifyError(msg, stderr) {
|
|
143
|
+
const haystack = `${msg} ${stderr}`.toLowerCase();
|
|
144
|
+
// Cloudflare's quick-tunnel rate-limit surfaces as 429 / error 1015.
|
|
145
|
+
// Both substrings appear in cloudflared's stderr on rate-limit.
|
|
146
|
+
if (haystack.includes('429') || haystack.includes('1015') || haystack.includes('rate limit') || haystack.includes('too many requests')) {
|
|
147
|
+
return new Error(`rate-limited: cloudflared quick-tunnel rate-limited (${msg})`);
|
|
148
|
+
}
|
|
149
|
+
if (haystack.includes('enoent') || haystack.includes('not found') || haystack.includes('binary-missing')) {
|
|
150
|
+
return new Error(`binary-missing: ${msg}`);
|
|
151
|
+
}
|
|
152
|
+
if (haystack.includes('dns') || haystack.includes('eai_again') || haystack.includes('econnrefused') || haystack.includes('network')) {
|
|
153
|
+
return new Error(`network: ${msg}`);
|
|
154
|
+
}
|
|
155
|
+
// Preserve as-is so the manager's classifier sees the original prefix.
|
|
156
|
+
return new Error(msg);
|
|
157
|
+
}
|
|
158
|
+
async stopHandle(tunnel) {
|
|
159
|
+
// Force-stop with PID escalation, mirroring TunnelManager.forceStop's
|
|
160
|
+
// SIGINT → SIGKILL pattern. This is security-load-bearing per spec
|
|
161
|
+
// Part 5 (relay teardown must guarantee the child process dies).
|
|
162
|
+
const proc = tunnel.process;
|
|
163
|
+
const pid = proc?.pid;
|
|
164
|
+
try {
|
|
165
|
+
tunnel.stop();
|
|
166
|
+
}
|
|
167
|
+
catch { /* may already be dead */ }
|
|
168
|
+
if (!pid)
|
|
169
|
+
return;
|
|
170
|
+
// Poll for exit; escalate to SIGKILL after 5s.
|
|
171
|
+
const timeoutMs = 5_000;
|
|
172
|
+
const exited = await new Promise((resolve) => {
|
|
173
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
174
|
+
try {
|
|
175
|
+
process.kill(pid, 0);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
resolve(true);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const poll = setInterval(() => {
|
|
183
|
+
try {
|
|
184
|
+
process.kill(pid, 0);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
clearInterval(poll);
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
resolve(true);
|
|
190
|
+
}
|
|
191
|
+
}, 200);
|
|
192
|
+
});
|
|
193
|
+
if (!exited) {
|
|
194
|
+
try {
|
|
195
|
+
process.kill(pid, 'SIGKILL');
|
|
196
|
+
}
|
|
197
|
+
catch { /* already dead */ }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=CloudflareQuickProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloudflareQuickProvider.js","sourceRoot":"","sources":["../../src/tunnel/CloudflareQuickProvider.ts"],"names":[],"mappings":"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"}
|