cyrus-cloudflare-tunnel-client 0.2.73 → 0.2.75

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.
@@ -1,29 +1,87 @@
1
1
  import { EventEmitter } from "node:events";
2
- import type { CloudflareTunnelClientEvents } from "./types.js";
2
+ import { Tunnel } from "cloudflared";
3
+ import type { CloudflareTunnelClientEvents, CloudflareTunnelRecoveryOptions } from "./types.js";
3
4
  export declare interface CloudflareTunnelClient {
4
5
  on<K extends keyof CloudflareTunnelClientEvents>(event: K, listener: CloudflareTunnelClientEvents[K]): this;
5
6
  emit<K extends keyof CloudflareTunnelClientEvents>(event: K, ...args: Parameters<CloudflareTunnelClientEvents[K]>): boolean;
6
7
  }
8
+ /**
9
+ * Default recovery policy for a cloudflared child that exits after it had
10
+ * connected. Delays double from 1s up to a 60s cap; after 10 consecutive
11
+ * failed restarts the client stops retrying and stays disconnected.
12
+ */
13
+ export declare const DEFAULT_TUNNEL_RECOVERY_OPTIONS: Required<CloudflareTunnelRecoveryOptions>;
14
+ export interface CloudflareTunnelClientOptions extends CloudflareTunnelRecoveryOptions {
15
+ /** Creates the cloudflared Tunnel (defaults to Tunnel.withToken); test seam */
16
+ createTunnel?: (token: string) => Tunnel;
17
+ }
7
18
  /**
8
19
  * Cloudflare tunnel client for establishing tunnels to local services
9
20
  * Handles ONLY tunnel establishment - HTTP handling is done by SharedApplicationServer
21
+ *
22
+ * The client is the single owner of the cloudflared child process. If a child
23
+ * that had connected exits unexpectedly while the client is still running, the
24
+ * client respawns it with bounded exponential backoff, never before the
25
+ * previous child's exit is confirmed. Startup failures and deliberate
26
+ * disconnects never trigger a respawn.
27
+ *
28
+ * Reentrancy: every state transition completes before any event is emitted,
29
+ * and nothing that could start work runs after an emit unless the client is
30
+ * still current, so listeners may call disconnect() from any event.
10
31
  */
11
32
  export declare class CloudflareTunnelClient extends EventEmitter {
12
- private tunnelProcess;
33
+ /** The owned child; held until its exit is confirmed (or it never spawned) */
34
+ private tunnel;
35
+ /** Incremented per spawned child and on loss/disconnect; fences stale callbacks */
36
+ private generation;
37
+ /** The single pending timer: connect timeout, kill wait, or restart backoff */
38
+ private timer;
39
+ /** A lost child was killed; the restart waits for its exit */
40
+ private awaitingExit;
41
+ private pendingRestartDelay;
42
+ private state;
43
+ private startup;
44
+ private restartAttempts;
45
+ private connectedAt;
13
46
  private tunnelUrl;
14
47
  private connected;
15
48
  private connectionCount;
16
49
  private cloudflareToken;
17
50
  private localPort;
18
- constructor(cloudflareToken: string, localPort: number, onReady?: (tunnelUrl: string) => void);
51
+ private options;
52
+ private createTunnel;
53
+ constructor(cloudflareToken: string, localPort: number, onReady?: (tunnelUrl: string) => void, options?: CloudflareTunnelClientOptions);
19
54
  /**
20
- * Start the Cloudflare tunnel
55
+ * Start the Cloudflare tunnel. Resolves once the first connection is
56
+ * established; rejects if the child exits, fails to spawn, the connect
57
+ * timeout elapses, or disconnect() is called first. Can only be called once.
21
58
  */
22
59
  startTunnel(): Promise<void>;
23
60
  /**
24
- * Wait for tunnel URL to be available
61
+ * Spawn a cloudflared child owned by this client and arm its connect
62
+ * timeout. Throws (owning nothing) if the tunnel cannot be created.
63
+ */
64
+ private spawnChild;
65
+ /** First connection of the current child: update state and settle startup */
66
+ private markConnected;
67
+ /**
68
+ * The current child is gone (exited, failed to spawn, or timed out before
69
+ * connecting). During startup this fails startTunnel(); afterwards it
70
+ * schedules a bounded restart once the child's exit is confirmed.
71
+ */
72
+ private handleChildLoss;
73
+ /** An owned child has actually exited (or never spawned) */
74
+ private releaseChild;
75
+ private scheduleRestart;
76
+ private handleKillWaitTimeout;
77
+ private setTimer;
78
+ private clearTimer;
79
+ /**
80
+ * Kill the owned child (if still alive) and release it. Only used when no
81
+ * further child will be spawned. Failed children are killed immediately;
82
+ * deliberate shutdowns get a graceful SIGINT first.
25
83
  */
26
- private waitForTunnelToConnect;
84
+ private stopChild;
27
85
  /**
28
86
  * Get the tunnel URL
29
87
  */
@@ -33,7 +91,9 @@ export declare class CloudflareTunnelClient extends EventEmitter {
33
91
  */
34
92
  isConnected(): boolean;
35
93
  /**
36
- * Disconnect and cleanup
94
+ * Disconnect and cleanup. Cancels any pending restart or startup and kills
95
+ * the owned child; the client will not respawn afterwards. Safe to call
96
+ * from any event listener.
37
97
  */
38
98
  disconnect(): void;
39
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CloudflareTunnelClient.d.ts","sourceRoot":"","sources":["../src/CloudflareTunnelClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAE/D,MAAM,CAAC,OAAO,WAAW,sBAAsB;IAC9C,EAAE,CAAC,CAAC,SAAS,MAAM,4BAA4B,EAC9C,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,4BAA4B,CAAC,CAAC,CAAC,GACvC,IAAI,CAAC;IACR,IAAI,CAAC,CAAC,SAAS,MAAM,4BAA4B,EAChD,KAAK,EAAE,CAAC,EACR,GAAG,IAAI,EAAE,UAAU,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC,GAClD,OAAO,CAAC;CACX;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,YAAY;IACvD,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,SAAS,CAAS;IAE1B,YACC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAUrC;IAED;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CA8DjC;IAED;;OAEG;YACW,sBAAsB;IAYpC;;OAEG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5B;IAED;;OAEG;IACH,WAAW,IAAI,OAAO,CAErB;IAED;;OAEG;IACH,UAAU,IAAI,IAAI,CASjB;CACD"}
1
+ {"version":3,"file":"CloudflareTunnelClient.d.ts","sourceRoot":"","sources":["../src/CloudflareTunnelClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAgB,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EACX,4BAA4B,EAC5B,+BAA+B,EAC/B,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,OAAO,WAAW,sBAAsB;IAC9C,EAAE,CAAC,CAAC,SAAS,MAAM,4BAA4B,EAC9C,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,4BAA4B,CAAC,CAAC,CAAC,GACvC,IAAI,CAAC;IACR,IAAI,CAAC,CAAC,SAAS,MAAM,4BAA4B,EAChD,KAAK,EAAE,CAAC,EACR,GAAG,IAAI,EAAE,UAAU,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC,GAClD,OAAO,CAAC;CACX;AAED;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CAAC,+BAA+B,CAgBpF,CAAC;AAEH,MAAM,WAAW,6BAChB,SAAQ,+BAA+B;IACvC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACzC;AAeD;;;;;;;;;;;;;GAaG;AACH,qBAAa,sBAAuB,SAAQ,YAAY;IACvD,8EAA8E;IAC9E,OAAO,CAAC,MAAM,CAAuB;IACrC,mFAAmF;IACnF,OAAO,CAAC,UAAU,CAAK;IACvB,+EAA+E;IAC/E,OAAO,CAAC,KAAK,CAA+B;IAC5C,8DAA8D;IAC9D,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAGC;IAChB,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAA4C;IAC3D,OAAO,CAAC,YAAY,CAA4B;IAEhD,YACC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EACrC,OAAO,GAAE,6BAAkC,EAc3C;IAED;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAuCjC;IAED;;;OAGG;IACH,OAAO,CAAC,UAAU;IAgFlB,6EAA6E;IAC7E,OAAO,CAAC,aAAa;IAkBrB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAgEvB,4DAA4D;IAC5D,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,UAAU;IAOlB;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAoBjB;;OAEG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5B;IAED;;OAEG;IACH,WAAW,IAAI,OAAO,CAErB;IAED;;;;OAIG;IACH,UAAU,IAAI,IAAI,CAiBjB;CACD"}
@@ -1,93 +1,322 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { existsSync } from "node:fs";
3
3
  import { bin, install, Tunnel } from "cloudflared";
4
+ /**
5
+ * Default recovery policy for a cloudflared child that exits after it had
6
+ * connected. Delays double from 1s up to a 60s cap; after 10 consecutive
7
+ * failed restarts the client stops retrying and stays disconnected.
8
+ */
9
+ export const DEFAULT_TUNNEL_RECOVERY_OPTIONS = {
10
+ // 120s: slow DNS resolvers (e.g. dead IPv6 nameservers ahead of the
11
+ // working one) can delay the first connection past 30s
12
+ connectTimeoutMs: 120000,
13
+ initialRestartDelayMs: 1000,
14
+ maxRestartDelayMs: 60000,
15
+ maxRestartAttempts: 10,
16
+ // A child that stayed connected this long counts as healthy, so its exit
17
+ // starts a fresh retry budget instead of continuing the previous one
18
+ stableConnectionMs: 60000,
19
+ // Grace period after SIGINT before SIGKILL on deliberate shutdown
20
+ killGraceMs: 5000,
21
+ // Max wait for a killed child to exit before a restart; if its exit is
22
+ // never confirmed, recovery stops rather than run a second child
23
+ killWaitMs: 10000,
24
+ };
25
+ const isAlive = (tunnel) => tunnel.process.pid !== undefined &&
26
+ tunnel.process.exitCode === null &&
27
+ tunnel.process.signalCode === null;
4
28
  /**
5
29
  * Cloudflare tunnel client for establishing tunnels to local services
6
30
  * Handles ONLY tunnel establishment - HTTP handling is done by SharedApplicationServer
31
+ *
32
+ * The client is the single owner of the cloudflared child process. If a child
33
+ * that had connected exits unexpectedly while the client is still running, the
34
+ * client respawns it with bounded exponential backoff, never before the
35
+ * previous child's exit is confirmed. Startup failures and deliberate
36
+ * disconnects never trigger a respawn.
37
+ *
38
+ * Reentrancy: every state transition completes before any event is emitted,
39
+ * and nothing that could start work runs after an emit unless the client is
40
+ * still current, so listeners may call disconnect() from any event.
7
41
  */
8
42
  export class CloudflareTunnelClient extends EventEmitter {
9
- tunnelProcess = null;
43
+ /** The owned child; held until its exit is confirmed (or it never spawned) */
44
+ tunnel = null;
45
+ /** Incremented per spawned child and on loss/disconnect; fences stale callbacks */
46
+ generation = 0;
47
+ /** The single pending timer: connect timeout, kill wait, or restart backoff */
48
+ timer = null;
49
+ /** A lost child was killed; the restart waits for its exit */
50
+ awaitingExit = false;
51
+ pendingRestartDelay = 0;
52
+ state = "idle";
53
+ startup = null;
54
+ restartAttempts = 0;
55
+ connectedAt = 0;
10
56
  tunnelUrl = null;
11
57
  connected = false;
12
58
  connectionCount = 0;
13
59
  cloudflareToken;
14
60
  localPort;
15
- constructor(cloudflareToken, localPort, onReady) {
61
+ options;
62
+ createTunnel;
63
+ constructor(cloudflareToken, localPort, onReady, options = {}) {
16
64
  super();
17
65
  this.cloudflareToken = cloudflareToken;
18
66
  this.localPort = localPort;
67
+ const { createTunnel, ...recovery } = options;
68
+ this.options = { ...DEFAULT_TUNNEL_RECOVERY_OPTIONS, ...recovery };
69
+ // Create tunnel with token-based authentication (no URL needed for remotely-managed tunnels)
70
+ this.createTunnel = createTunnel ?? ((token) => Tunnel.withToken(token));
19
71
  // Set up onReady callback if provided
20
72
  if (onReady) {
21
73
  this.on("ready", onReady);
22
74
  }
23
75
  }
24
76
  /**
25
- * Start the Cloudflare tunnel
77
+ * Start the Cloudflare tunnel. Resolves once the first connection is
78
+ * established; rejects if the child exits, fails to spawn, the connect
79
+ * timeout elapses, or disconnect() is called first. Can only be called once.
26
80
  */
27
81
  async startTunnel() {
82
+ if (this.state !== "idle") {
83
+ throw new Error(`Cloudflare tunnel already started (state: ${this.state})`);
84
+ }
85
+ this.state = "starting";
28
86
  try {
29
87
  // Ensure cloudflared binary is installed
30
88
  if (!existsSync(bin)) {
31
89
  await install(bin);
32
90
  }
91
+ if (this.state !== "starting") {
92
+ throw new Error("Cloudflare tunnel disconnected during startup");
93
+ }
33
94
  console.log(`Starting tunnel to localhost:${this.localPort}`);
34
- // Create tunnel with token-based authentication (no URL needed for remotely-managed tunnels)
35
- const tunnel = Tunnel.withToken(this.cloudflareToken);
36
- // Listen for URL event (from ConfigHandler for token-based tunnels)
37
- tunnel.on("url", (url) => {
38
- // Ensure URL has protocol for token-based tunnels
39
- if (!url.startsWith("http")) {
40
- url = `https://${url}`;
41
- }
42
- if (!this.tunnelUrl) {
43
- this.tunnelUrl = url;
44
- this.emit("ready", this.tunnelUrl);
45
- }
46
- });
47
- // Listen for connection event (Cloudflare establishes 4 connections per tunnel)
48
- tunnel.on("connected", (connection) => {
49
- this.connectionCount++;
50
- console.log(`Cloudflare tunnel connection ${this.connectionCount}/4 established:`, connection);
51
- // Emit 'connected' event for each connection (for external listeners)
52
- this.emit("connected", connection);
53
- // Mark as connected on first connection, but log all 4
54
- if (!this.connected) {
55
- this.connected = true;
56
- this.emit("connect");
57
- }
95
+ await new Promise((resolve, reject) => {
96
+ this.startup = { resolve, reject };
97
+ this.spawnChild();
58
98
  });
59
- // Listen for error event
60
- tunnel.on("error", (error) => {
61
- this.emit("error", error);
62
- });
63
- // Listen for exit event
64
- tunnel.on("exit", (code) => {
65
- this.connected = false;
66
- this.connectionCount = 0; // Reset count on disconnect for fresh reconnection logs
67
- this.emit("disconnect", `Tunnel process exited with code ${code}`);
68
- });
69
- // Wait for tunnel URL to be available (with timeout)
70
- // 120s: slow DNS resolvers (e.g. dead IPv6 nameservers ahead of the
71
- // working one) can delay the first connection past 30s
72
- await this.waitForTunnelToConnect(120000);
73
99
  }
74
100
  catch (error) {
75
- this.emit("error", error);
101
+ this.startup = null;
102
+ // Re-read state: disconnect() or a child loss may have changed it
103
+ const state = this.state;
104
+ if (state === "starting") {
105
+ this.state = "failed";
106
+ this.generation++;
107
+ this.clearTimer();
108
+ this.stopChild(true);
109
+ }
110
+ // A deliberate disconnect during startup is not an error condition
111
+ if (state !== "stopped") {
112
+ this.emit("error", error);
113
+ }
76
114
  throw error;
77
115
  }
78
116
  }
79
117
  /**
80
- * Wait for tunnel URL to be available
118
+ * Spawn a cloudflared child owned by this client and arm its connect
119
+ * timeout. Throws (owning nothing) if the tunnel cannot be created.
120
+ */
121
+ spawnChild() {
122
+ const generation = ++this.generation;
123
+ const isCurrent = () => generation === this.generation &&
124
+ (this.state === "starting" ||
125
+ this.state === "recovering" ||
126
+ this.state === "running");
127
+ this.connectionCount = 0;
128
+ const tunnel = this.createTunnel(this.cloudflareToken);
129
+ this.tunnel = tunnel;
130
+ // Ownership release is not generation-gated: a killed child stays owned
131
+ // until it has actually exited
132
+ tunnel.on("exit", () => this.releaseChild(tunnel));
133
+ // Listen for exit event
134
+ tunnel.on("exit", (code, signal) => {
135
+ if (!isCurrent())
136
+ return;
137
+ this.handleChildLoss(`Tunnel process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
138
+ });
139
+ // Listen for error event. Errors are informational unless the child
140
+ // never spawned (no pid), in which case no 'exit' will follow.
141
+ tunnel.on("error", (error) => {
142
+ const neverSpawned = tunnel.process.pid === undefined;
143
+ if (neverSpawned)
144
+ this.releaseChild(tunnel);
145
+ if (generation !== this.generation)
146
+ return;
147
+ if (neverSpawned && isCurrent()) {
148
+ this.handleChildLoss(`Tunnel process failed to start: ${error.message}`);
149
+ }
150
+ this.emit("error", error);
151
+ });
152
+ // Listen for URL event (from ConfigHandler for token-based tunnels)
153
+ tunnel.on("url", (url) => {
154
+ if (!isCurrent())
155
+ return;
156
+ // Ensure URL has protocol for token-based tunnels
157
+ if (!url.startsWith("http")) {
158
+ url = `https://${url}`;
159
+ }
160
+ if (!this.tunnelUrl) {
161
+ this.tunnelUrl = url;
162
+ this.emit("ready", this.tunnelUrl);
163
+ }
164
+ });
165
+ // Listen for connection event (Cloudflare establishes 4 connections per tunnel)
166
+ tunnel.on("connected", (connection) => {
167
+ if (!isCurrent())
168
+ return;
169
+ this.connectionCount++;
170
+ console.log(`Cloudflare tunnel connection ${this.connectionCount}/4 established:`, connection);
171
+ // Mark as connected on first connection, but log all 4
172
+ const first = !this.connected;
173
+ if (first) {
174
+ this.markConnected();
175
+ }
176
+ // Emit 'connected' event for each connection (for external listeners)
177
+ this.emit("connected", connection);
178
+ if (first && isCurrent()) {
179
+ this.emit("connect");
180
+ }
181
+ });
182
+ this.setTimer(() => {
183
+ this.handleChildLoss(`Timeout waiting for tunnel to connect after ${this.options.connectTimeoutMs}ms`);
184
+ }, this.options.connectTimeoutMs);
185
+ }
186
+ /** First connection of the current child: update state and settle startup */
187
+ markConnected() {
188
+ this.clearTimer();
189
+ this.connected = true;
190
+ this.connectedAt = Date.now();
191
+ const recovered = this.state === "recovering";
192
+ this.state = "running";
193
+ if (recovered) {
194
+ console.log(`Cloudflare tunnel recovered after ${this.restartAttempts} restart attempt(s)`);
195
+ }
196
+ const startup = this.startup;
197
+ this.startup = null;
198
+ startup?.resolve();
199
+ }
200
+ /**
201
+ * The current child is gone (exited, failed to spawn, or timed out before
202
+ * connecting). During startup this fails startTunnel(); afterwards it
203
+ * schedules a bounded restart once the child's exit is confirmed.
81
204
  */
82
- async waitForTunnelToConnect(timeout) {
83
- const startTime = Date.now();
84
- while (!this.connected) {
85
- if (Date.now() - startTime > timeout) {
86
- throw new Error("Timeout waiting for tunnel URL");
205
+ handleChildLoss(reason) {
206
+ this.clearTimer();
207
+ // Fence any remaining callbacks from the lost child
208
+ this.generation++;
209
+ const wasConnected = this.connected;
210
+ this.connected = false;
211
+ this.connectionCount = 0; // Reset count on disconnect for fresh reconnection logs
212
+ if (this.state === "starting") {
213
+ this.state = "failed";
214
+ this.stopChild(true);
215
+ const startup = this.startup;
216
+ this.startup = null;
217
+ // startTunnel() reports this rejection as an 'error' event
218
+ startup?.reject(new Error(`Cloudflare tunnel failed to start: ${reason}`));
219
+ return;
220
+ }
221
+ if (wasConnected &&
222
+ Date.now() - this.connectedAt >= this.options.stableConnectionMs) {
223
+ this.restartAttempts = 0;
224
+ }
225
+ if (this.restartAttempts >= this.options.maxRestartAttempts) {
226
+ this.state = "failed";
227
+ this.stopChild(true);
228
+ const message = `Cloudflare tunnel recovery gave up after ${this.restartAttempts} consecutive failed restart attempts (${reason}); restart Cyrus to restore the tunnel`;
229
+ console.error(message);
230
+ if (wasConnected)
231
+ this.emit("disconnect", reason);
232
+ if (this.state === "failed")
233
+ this.emit("error", new Error(message));
234
+ return;
235
+ }
236
+ this.state = "recovering";
237
+ this.restartAttempts++;
238
+ this.pendingRestartDelay = Math.min(this.options.initialRestartDelayMs * 2 ** (this.restartAttempts - 1), this.options.maxRestartDelayMs);
239
+ const owned = this.tunnel;
240
+ if (owned && isAlive(owned)) {
241
+ // Never run two children: restart only after this one has exited
242
+ this.awaitingExit = true;
243
+ owned.process.kill("SIGKILL");
244
+ this.setTimer(() => this.handleKillWaitTimeout(owned), this.options.killWaitMs);
245
+ }
246
+ else {
247
+ this.tunnel = null;
248
+ this.scheduleRestart();
249
+ }
250
+ console.warn(`${reason}; restarting Cloudflare tunnel in ${this.pendingRestartDelay}ms (attempt ${this.restartAttempts}/${this.options.maxRestartAttempts})`);
251
+ if (wasConnected)
252
+ this.emit("disconnect", reason);
253
+ }
254
+ /** An owned child has actually exited (or never spawned) */
255
+ releaseChild(tunnel) {
256
+ if (this.tunnel !== tunnel)
257
+ return;
258
+ this.tunnel = null;
259
+ if (this.awaitingExit && this.state === "recovering") {
260
+ this.awaitingExit = false;
261
+ this.scheduleRestart();
262
+ }
263
+ }
264
+ scheduleRestart() {
265
+ this.setTimer(() => {
266
+ if (this.state !== "recovering" || this.tunnel)
267
+ return;
268
+ try {
269
+ this.spawnChild();
87
270
  }
88
- await new Promise((resolve) => setTimeout(resolve, 100));
271
+ catch (error) {
272
+ this.handleChildLoss(`Tunnel process failed to spawn: ${error instanceof Error ? error.message : String(error)}`);
273
+ }
274
+ }, this.pendingRestartDelay);
275
+ }
276
+ handleKillWaitTimeout(tunnel) {
277
+ this.awaitingExit = false;
278
+ this.state = "failed";
279
+ const message = `Cloudflare tunnel recovery stopped: previous cloudflared process (pid ${tunnel.process.pid}) did not exit within ${this.options.killWaitMs}ms of SIGKILL; not starting another to avoid running two; restart Cyrus to restore the tunnel`;
280
+ console.error(message);
281
+ this.emit("error", new Error(message));
282
+ }
283
+ setTimer(callback, delay) {
284
+ this.clearTimer();
285
+ this.timer = setTimeout(() => {
286
+ this.timer = null;
287
+ callback();
288
+ }, delay);
289
+ }
290
+ clearTimer() {
291
+ if (this.timer) {
292
+ clearTimeout(this.timer);
293
+ this.timer = null;
89
294
  }
90
295
  }
296
+ /**
297
+ * Kill the owned child (if still alive) and release it. Only used when no
298
+ * further child will be spawned. Failed children are killed immediately;
299
+ * deliberate shutdowns get a graceful SIGINT first.
300
+ */
301
+ stopChild(force) {
302
+ const tunnel = this.tunnel;
303
+ this.tunnel = null;
304
+ if (!tunnel || !isAlive(tunnel))
305
+ return;
306
+ const child = tunnel.process;
307
+ if (force) {
308
+ child.kill("SIGKILL");
309
+ return;
310
+ }
311
+ tunnel.stop();
312
+ const escalation = setTimeout(() => {
313
+ if (isAlive(tunnel)) {
314
+ child.kill("SIGKILL");
315
+ }
316
+ }, this.options.killGraceMs);
317
+ escalation.unref();
318
+ child.once("exit", () => clearTimeout(escalation));
319
+ }
91
320
  /**
92
321
  * Get the tunnel URL
93
322
  */
@@ -101,16 +330,25 @@ export class CloudflareTunnelClient extends EventEmitter {
101
330
  return this.connected;
102
331
  }
103
332
  /**
104
- * Disconnect and cleanup
333
+ * Disconnect and cleanup. Cancels any pending restart or startup and kills
334
+ * the owned child; the client will not respawn afterwards. Safe to call
335
+ * from any event listener.
105
336
  */
106
337
  disconnect() {
107
- if (this.tunnelProcess) {
108
- this.tunnelProcess.kill();
109
- this.tunnelProcess = null;
110
- }
338
+ const previousState = this.state;
339
+ this.state = "stopped";
340
+ this.generation++;
341
+ this.awaitingExit = false;
342
+ this.clearTimer();
343
+ this.stopChild(false);
111
344
  this.connected = false;
112
345
  this.connectionCount = 0; // Reset count on disconnect for fresh reconnection logs
113
- this.emit("disconnect", "Client disconnected");
346
+ const startup = this.startup;
347
+ this.startup = null;
348
+ startup?.reject(new Error("Cloudflare tunnel disconnected during startup"));
349
+ if (previousState !== "stopped") {
350
+ this.emit("disconnect", "Client disconnected");
351
+ }
114
352
  }
115
353
  }
116
354
  //# sourceMappingURL=CloudflareTunnelClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CloudflareTunnelClient.js","sourceRoot":"","sources":["../src/CloudflareTunnelClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CloudflareTunnelClient.js","sourceRoot":"","sources":["../src/CloudflareTunnelClient.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,8 @@ export interface ConfigApiResponse {
16
16
  config?: {
17
17
  cloudflareToken: string;
18
18
  apiKey: string;
19
+ /** Optional hosted-managed listener port; absent preserves legacy behavior. */
20
+ serverPort?: number;
19
21
  };
20
22
  error?: string;
21
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigApiClient.d.ts","sourceRoot":"","sources":["../src/ConfigApiClient.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE;QACR,eAAe,EAAE,MAAM,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;KACf,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AAEH,qBAAa,eAAe;IAC3B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAI9B;;;;OAIG;IACH,OAAa,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAyDlE;IAED;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAMnD;CACD"}
1
+ {"version":3,"file":"ConfigApiClient.d.ts","sourceRoot":"","sources":["../src/ConfigApiClient.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE;QACR,eAAe,EAAE,MAAM,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,+EAA+E;QAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AAEH,qBAAa,eAAe;IAC3B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAI9B;;;;OAIG;IACH,OAAa,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiElE;IAED;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAWnD;CACD"}
@@ -40,7 +40,9 @@ export class ConfigApiClient {
40
40
  }
41
41
  // Call config API with auth key
42
42
  const url = `${ConfigApiClient.getConfigApiUrl()}?auth_key=${encodeURIComponent(authKey)}`;
43
- const response = await fetch(url);
43
+ const response = await fetch(url, {
44
+ headers: { "X-Cyrus-Config-Capabilities": "self-host-port-v1" },
45
+ });
44
46
  if (!response.ok) {
45
47
  const errorText = await response.text();
46
48
  return {
@@ -63,6 +65,12 @@ export class ConfigApiClient {
63
65
  error: "Config API response missing required fields",
64
66
  };
65
67
  }
68
+ if (!ConfigApiClient.isValid(data)) {
69
+ return {
70
+ success: false,
71
+ error: "Config API response contains an invalid listener port",
72
+ };
73
+ }
66
74
  return data;
67
75
  }
68
76
  catch (error) {
@@ -84,7 +92,12 @@ export class ConfigApiClient {
84
92
  static isValid(response) {
85
93
  return (response.success &&
86
94
  !!response.config?.cloudflareToken &&
87
- !!response.config?.apiKey);
95
+ !!response.config?.apiKey &&
96
+ (response.config.serverPort === undefined ||
97
+ (typeof response.config.serverPort === "number" &&
98
+ Number.isInteger(response.config.serverPort) &&
99
+ response.config.serverPort >= 1 &&
100
+ response.config.serverPort <= 65535)));
88
101
  }
89
102
  }
90
103
  //# sourceMappingURL=ConfigApiClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigApiClient.js","sourceRoot":"","sources":["../src/ConfigApiClient.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,yBAAyB,CAAC;AAE/D;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,qBAAqB,CAAC;AAC3D,CAAC;AAcD;;;GAGG;AACH,yGAAyG;AACzG,MAAM,OAAO,eAAe;IAC3B;;OAEG;IACK,MAAM,CAAC,eAAe;QAC7B,OAAO,GAAG,cAAc,EAAE,aAAa,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAe;QACrC,IAAI,CAAC;YACJ,oBAAoB;YACpB,IACC,CAAC,OAAO;gBACR,OAAO,OAAO,KAAK,QAAQ;gBAC3B,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAC1B,CAAC;gBACF,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,sBAAsB;iBAC7B,CAAC;YACH,CAAC;YAED,gCAAgC;YAChC,MAAM,GAAG,GAAG,GAAG,eAAe,CAAC,eAAe,EAAE,aAAa,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3F,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YAElC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAClB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACxC,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,8BAA8B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,MAAM,SAAS,EAAE;iBAC5F,CAAC;YACH,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAsB,CAAC;YAE1D,8BAA8B;YAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACnC,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,yCAAyC;iBAC9D,CAAC;YACH,CAAC;YAED,2BAA2B;YAC3B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACzD,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,6CAA6C;iBACpD,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;gBAC5B,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,8BAA8B,KAAK,CAAC,OAAO,EAAE;iBACpD,CAAC;YACH,CAAC;YACD,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,0CAA0C;aACjD,CAAC;QACH,CAAC;IACF,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,QAA2B;QACzC,OAAO,CACN,QAAQ,CAAC,OAAO;YAChB,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,eAAe;YAClC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CACzB,CAAC;IACH,CAAC;CACD"}
1
+ {"version":3,"file":"ConfigApiClient.js","sourceRoot":"","sources":["../src/ConfigApiClient.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,yBAAyB,CAAC;AAE/D;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,qBAAqB,CAAC;AAC3D,CAAC;AAgBD;;;GAGG;AACH,yGAAyG;AACzG,MAAM,OAAO,eAAe;IAC3B;;OAEG;IACK,MAAM,CAAC,eAAe;QAC7B,OAAO,GAAG,cAAc,EAAE,aAAa,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAe;QACrC,IAAI,CAAC;YACJ,oBAAoB;YACpB,IACC,CAAC,OAAO;gBACR,OAAO,OAAO,KAAK,QAAQ;gBAC3B,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAC1B,CAAC;gBACF,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,sBAAsB;iBAC7B,CAAC;YACH,CAAC;YAED,gCAAgC;YAChC,MAAM,GAAG,GAAG,GAAG,eAAe,CAAC,eAAe,EAAE,aAAa,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3F,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBACjC,OAAO,EAAE,EAAE,6BAA6B,EAAE,mBAAmB,EAAE;aAC/D,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAClB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACxC,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,8BAA8B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,MAAM,SAAS,EAAE;iBAC5F,CAAC;YACH,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAsB,CAAC;YAE1D,8BAA8B;YAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACnC,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,yCAAyC;iBAC9D,CAAC;YACH,CAAC;YAED,2BAA2B;YAC3B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACzD,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,6CAA6C;iBACpD,CAAC;YACH,CAAC;YACD,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpC,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,uDAAuD;iBAC9D,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;gBAC5B,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,8BAA8B,KAAK,CAAC,OAAO,EAAE;iBACpD,CAAC;YACH,CAAC;YACD,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,0CAA0C;aACjD,CAAC;QACH,CAAC;IACF,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,QAA2B;QACzC,OAAO,CACN,QAAQ,CAAC,OAAO;YAChB,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,eAAe;YAClC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM;YACzB,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS;gBACxC,CAAC,OAAO,QAAQ,CAAC,MAAM,CAAC,UAAU,KAAK,QAAQ;oBAC9C,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC;oBAC5C,QAAQ,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC;oBAC/B,QAAQ,CAAC,MAAM,CAAC,UAAU,IAAI,KAAK,CAAC,CAAC,CACvC,CAAC;IACH,CAAC;CACD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ConfigApiClient.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigApiClient.test.d.ts","sourceRoot":"","sources":["../src/ConfigApiClient.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,42 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { ConfigApiClient } from "./ConfigApiClient.js";
3
+ afterEach(() => vi.unstubAllGlobals());
4
+ describe("hosted listener port contract", () => {
5
+ it.each([
6
+ undefined,
7
+ 3456,
8
+ 19119,
9
+ 1,
10
+ 65535,
11
+ ])("accepts compatible port %s", async (serverPort) => {
12
+ const response = {
13
+ success: true,
14
+ config: { cloudflareToken: "fixture", apiKey: "fixture", serverPort },
15
+ };
16
+ vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json(response)));
17
+ expect(ConfigApiClient.isValid(response)).toBe(true);
18
+ expect(await ConfigApiClient.getConfig("fixture-auth")).toEqual(response);
19
+ expect(fetch).toHaveBeenCalledWith(expect.any(String), {
20
+ headers: { "X-Cyrus-Config-Capabilities": "self-host-port-v1" },
21
+ });
22
+ });
23
+ it.each([
24
+ null,
25
+ 0,
26
+ 65536,
27
+ -1,
28
+ 1.5,
29
+ "19119",
30
+ "19119\nOTHER=value",
31
+ {},
32
+ ])("rejects malformed port %s before auth writes", async (serverPort) => {
33
+ const response = {
34
+ success: true,
35
+ config: { cloudflareToken: "fixture", apiKey: "fixture", serverPort },
36
+ };
37
+ vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json(response)));
38
+ expect(ConfigApiClient.isValid(response)).toBe(false);
39
+ expect((await ConfigApiClient.getConfig("fixture-auth")).success).toBe(false);
40
+ });
41
+ });
42
+ //# sourceMappingURL=ConfigApiClient.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigApiClient.test.js","sourceRoot":"","sources":["../src/ConfigApiClient.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAC;AAEvC,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC9C,EAAE,CAAC,IAAI,CAAC;QACP,SAAS;QACT,IAAI;QACJ,KAAK;QACL,CAAC;QACD,KAAK;KACL,CAAC,CAAC,4BAA4B,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;QACrD,MAAM,QAAQ,GAAG;YAChB,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE;SACrE,CAAC;QACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,MAAM,eAAe,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1E,MAAM,CAAC,KAAK,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;YACtD,OAAO,EAAE,EAAE,6BAA6B,EAAE,mBAAmB,EAAE;SAC/D,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,IAAI,CAAC;QACP,IAAI;QACJ,CAAC;QACD,KAAK;QACL,CAAC,CAAC;QACF,GAAG;QACH,OAAO;QACP,oBAAoB;QACpB,EAAE;KACF,CAAC,CAAC,8CAA8C,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;QACvE,MAAM,QAAQ,GAAG;YAChB,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE;SACrE,CAAC;QACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,QAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/D,MAAM,CAAC,CAAC,MAAM,eAAe,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CACrE,KAAK,CACL,CAAC;IACH,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { CloudflareTunnelClient } from "./CloudflareTunnelClient.js";
1
+ export { CloudflareTunnelClient, type CloudflareTunnelClientOptions, } from "./CloudflareTunnelClient.js";
2
2
  export { ConfigApiClient, DEFAULT_CYRUS_APP_URL, getCyrusAppUrl, } from "./ConfigApiClient.js";
3
3
  export * from "./types.js";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACN,eAAe,EACf,qBAAqB,EACrB,cAAc,GACd,MAAM,sBAAsB,CAAC;AAC9B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,sBAAsB,EACtB,KAAK,6BAA6B,GAClC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACN,eAAe,EACf,qBAAqB,EACrB,cAAc,GACd,MAAM,sBAAsB,CAAC;AAC9B,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { CloudflareTunnelClient } from "./CloudflareTunnelClient.js";
1
+ export { CloudflareTunnelClient, } from "./CloudflareTunnelClient.js";
2
2
  export { ConfigApiClient, DEFAULT_CYRUS_APP_URL, getCyrusAppUrl, } from "./ConfigApiClient.js";
3
3
  export * from "./types.js";
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACN,eAAe,EACf,qBAAqB,EACrB,cAAc,GACd,MAAM,sBAAsB,CAAC;AAC9B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,sBAAsB,GAEtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACN,eAAe,EACf,qBAAqB,EACrB,cAAc,GACd,MAAM,sBAAsB,CAAC;AAC9B,cAAc,YAAY,CAAC"}
package/dist/types.d.ts CHANGED
@@ -8,4 +8,23 @@ export interface CloudflareTunnelClientEvents {
8
8
  error: (error: Error) => void;
9
9
  ready: (tunnelUrl: string) => void;
10
10
  }
11
+ /**
12
+ * Restart policy for a cloudflared child that exits after it had connected
13
+ */
14
+ export interface CloudflareTunnelRecoveryOptions {
15
+ /** Max time for a spawned child to report its first connection */
16
+ connectTimeoutMs?: number;
17
+ /** Delay before the first restart attempt; doubles per consecutive failure */
18
+ initialRestartDelayMs?: number;
19
+ /** Upper bound for the restart delay */
20
+ maxRestartDelayMs?: number;
21
+ /** Consecutive failed restarts before giving up (client stays disconnected) */
22
+ maxRestartAttempts?: number;
23
+ /** Connected duration after which an exit starts a fresh retry budget */
24
+ stableConnectionMs?: number;
25
+ /** Grace period after SIGINT before SIGKILL on deliberate disconnect */
26
+ killGraceMs?: number;
27
+ /** Max wait for a killed child to exit before restarting; recovery stops if exceeded */
28
+ killWaitMs?: number;
29
+ }
11
30
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACrC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACrC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC/C,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,wCAAwC;IACxC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyrus-cloudflare-tunnel-client",
3
- "version": "0.2.73",
3
+ "version": "0.2.75",
4
4
  "description": "Cloudflare tunnel client for receiving config updates and webhooks from cyrus-hosted",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "devDependencies": {
26
26
  "@types/node": "^20.12.7",
27
27
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
28
- "vitest": "^4.1.11"
28
+ "vitest": "^4.1.12"
29
29
  },
30
30
  "keywords": [
31
31
  "cloudflare",