openchrome-mcp 1.9.0 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +55 -1
  2. package/dist/cdp/client.d.ts +6 -0
  3. package/dist/cdp/client.d.ts.map +1 -1
  4. package/dist/cdp/client.js +13 -0
  5. package/dist/cdp/client.js.map +1 -1
  6. package/dist/chrome/headed-fallback.d.ts +3 -0
  7. package/dist/chrome/headed-fallback.d.ts.map +1 -1
  8. package/dist/chrome/headed-fallback.js +4 -0
  9. package/dist/chrome/headed-fallback.js.map +1 -1
  10. package/dist/cli/index.js +0 -0
  11. package/dist/config/global.d.ts +1 -0
  12. package/dist/config/global.d.ts.map +1 -1
  13. package/dist/config/global.js.map +1 -1
  14. package/dist/config/tool-tiers.d.ts.map +1 -1
  15. package/dist/config/tool-tiers.js +3 -0
  16. package/dist/config/tool-tiers.js.map +1 -1
  17. package/dist/connect/clipboard.d.ts +6 -0
  18. package/dist/connect/clipboard.d.ts.map +1 -0
  19. package/dist/connect/clipboard.js +54 -0
  20. package/dist/connect/clipboard.js.map +1 -0
  21. package/dist/connect/config-generator.d.ts +9 -0
  22. package/dist/connect/config-generator.d.ts.map +1 -0
  23. package/dist/connect/config-generator.js +43 -0
  24. package/dist/connect/config-generator.js.map +1 -0
  25. package/dist/connect/hosts.d.ts +9 -0
  26. package/dist/connect/hosts.d.ts.map +1 -0
  27. package/dist/connect/hosts.js +81 -0
  28. package/dist/connect/hosts.js.map +1 -0
  29. package/dist/connect/index.d.ts +8 -0
  30. package/dist/connect/index.d.ts.map +1 -0
  31. package/dist/connect/index.js +15 -0
  32. package/dist/connect/index.js.map +1 -0
  33. package/dist/connect/open-url.d.ts +6 -0
  34. package/dist/connect/open-url.d.ts.map +1 -0
  35. package/dist/connect/open-url.js +42 -0
  36. package/dist/connect/open-url.js.map +1 -0
  37. package/dist/connect/types.d.ts +48 -0
  38. package/dist/connect/types.d.ts.map +1 -0
  39. package/dist/connect/types.js +7 -0
  40. package/dist/connect/types.js.map +1 -0
  41. package/dist/dashboard/index.d.ts +3 -0
  42. package/dist/dashboard/index.d.ts.map +1 -1
  43. package/dist/dashboard/index.js +47 -0
  44. package/dist/dashboard/index.js.map +1 -1
  45. package/dist/dashboard/types.d.ts +1 -1
  46. package/dist/dashboard/types.d.ts.map +1 -1
  47. package/dist/dashboard/views/connect-view.d.ts +25 -0
  48. package/dist/dashboard/views/connect-view.d.ts.map +1 -0
  49. package/dist/dashboard/views/connect-view.js +135 -0
  50. package/dist/dashboard/views/connect-view.js.map +1 -0
  51. package/dist/dashboard/views/main-view.d.ts.map +1 -1
  52. package/dist/dashboard/views/main-view.js +1 -0
  53. package/dist/dashboard/views/main-view.js.map +1 -1
  54. package/dist/desktop/chrome-detector.d.ts +58 -0
  55. package/dist/desktop/chrome-detector.d.ts.map +1 -0
  56. package/dist/desktop/chrome-detector.js +211 -0
  57. package/dist/desktop/chrome-detector.js.map +1 -0
  58. package/dist/desktop/cli-coexistence.d.ts +90 -0
  59. package/dist/desktop/cli-coexistence.d.ts.map +1 -0
  60. package/dist/desktop/cli-coexistence.js +219 -0
  61. package/dist/desktop/cli-coexistence.js.map +1 -0
  62. package/dist/desktop/dashboard-state.d.ts +50 -0
  63. package/dist/desktop/dashboard-state.d.ts.map +1 -0
  64. package/dist/desktop/dashboard-state.js +155 -0
  65. package/dist/desktop/dashboard-state.js.map +1 -0
  66. package/dist/desktop/error-log.d.ts +53 -0
  67. package/dist/desktop/error-log.d.ts.map +1 -0
  68. package/dist/desktop/error-log.js +127 -0
  69. package/dist/desktop/error-log.js.map +1 -0
  70. package/dist/desktop/sidecar-manager.d.ts +80 -0
  71. package/dist/desktop/sidecar-manager.d.ts.map +1 -0
  72. package/dist/desktop/sidecar-manager.js +222 -0
  73. package/dist/desktop/sidecar-manager.js.map +1 -0
  74. package/dist/desktop/tunnel-failure-handler.d.ts +56 -0
  75. package/dist/desktop/tunnel-failure-handler.d.ts.map +1 -0
  76. package/dist/desktop/tunnel-failure-handler.js +157 -0
  77. package/dist/desktop/tunnel-failure-handler.js.map +1 -0
  78. package/dist/desktop/tunnel-manager.d.ts +85 -0
  79. package/dist/desktop/tunnel-manager.d.ts.map +1 -0
  80. package/dist/desktop/tunnel-manager.js +363 -0
  81. package/dist/desktop/tunnel-manager.js.map +1 -0
  82. package/dist/index.js +15 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/mcp-server.d.ts.map +1 -1
  85. package/dist/mcp-server.js +4 -0
  86. package/dist/mcp-server.js.map +1 -1
  87. package/dist/session-manager.d.ts +6 -0
  88. package/dist/session-manager.d.ts.map +1 -1
  89. package/dist/session-manager.js +12 -0
  90. package/dist/session-manager.js.map +1 -1
  91. package/dist/tools/connect.d.ts +7 -0
  92. package/dist/tools/connect.d.ts.map +1 -0
  93. package/dist/tools/connect.js +125 -0
  94. package/dist/tools/connect.js.map +1 -0
  95. package/dist/tools/index.d.ts.map +1 -1
  96. package/dist/tools/index.js +4 -0
  97. package/dist/tools/index.js.map +1 -1
  98. package/dist/tools/navigate.d.ts.map +1 -1
  99. package/dist/tools/navigate.js +15 -6
  100. package/dist/tools/navigate.js.map +1 -1
  101. package/dist/transports/http.d.ts +26 -1
  102. package/dist/transports/http.d.ts.map +1 -1
  103. package/dist/transports/http.js +161 -4
  104. package/dist/transports/http.js.map +1 -1
  105. package/dist/transports/index.d.ts +1 -0
  106. package/dist/transports/index.d.ts.map +1 -1
  107. package/dist/transports/index.js +1 -1
  108. package/dist/transports/index.js.map +1 -1
  109. package/package.json +1 -1
  110. package/dist/tools/click-element.d.ts +0 -8
  111. package/dist/tools/click-element.d.ts.map +0 -1
  112. package/dist/tools/click-element.js +0 -347
  113. package/dist/tools/click-element.js.map +0 -1
  114. package/dist/tools/wait-and-click.d.ts +0 -8
  115. package/dist/tools/wait-and-click.d.ts.map +0 -1
  116. package/dist/tools/wait-and-click.js +0 -208
  117. package/dist/tools/wait-and-click.js.map +0 -1
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Sidecar Manager — manages a Node.js child process sidecar with crash recovery.
3
+ * Detects crashes via process exit event, auto-restarts up to maxCrashes times
4
+ * within a sliding window, and handles port conflicts via auto-increment.
5
+ * Part of #524: Desktop App error handling + local fallback + CLI coexistence.
6
+ */
7
+ import { EventEmitter } from 'events';
8
+ export interface SidecarOptions {
9
+ /** Executable command, e.g. 'node' */
10
+ command: string;
11
+ /** Arguments, e.g. ['dist/index.js', 'serve'] */
12
+ args: string[];
13
+ /** Base port for the sidecar server. Default: 3100 */
14
+ basePort?: number;
15
+ /** Maximum crash count before giving up. Default: 3 */
16
+ maxCrashes?: number;
17
+ /** Sliding window in ms to count crashes. Default: 300000 (5 min) */
18
+ crashWindowMs?: number;
19
+ /** Additional environment variables for the child process */
20
+ env?: Record<string, string>;
21
+ }
22
+ export type SidecarStatus = 'stopped' | 'starting' | 'running' | 'restarting' | 'crashed' | 'exhausted';
23
+ export interface SidecarState {
24
+ status: SidecarStatus;
25
+ pid: number | null;
26
+ port: number;
27
+ crashCount: number;
28
+ lastError: string | null;
29
+ /** Milliseconds since last successful start, or 0 if not started */
30
+ uptime: number;
31
+ }
32
+ /**
33
+ * SidecarManager events:
34
+ *
35
+ * 'started' → { pid: number | undefined, port: number }
36
+ * 'restarting' → { crashCount: number, reason: string }
37
+ * 'running' → { pid: number | undefined, port: number }
38
+ * 'crashed' → { reason: string, crashCount: number }
39
+ * 'exhausted' → { crashCount: number, message: string }
40
+ * 'port-changed' → { oldPort: number, newPort: number }
41
+ * 'stderr' → { data: string }
42
+ */
43
+ export declare class SidecarManager extends EventEmitter {
44
+ private readonly options;
45
+ private child;
46
+ private status;
47
+ private currentPort;
48
+ private crashTimestamps;
49
+ private lastError;
50
+ private startedAt;
51
+ private intentionalStop;
52
+ private stderrLines;
53
+ private readonly maxStderrLines;
54
+ /** SIGKILL delay after SIGTERM in milliseconds */
55
+ private readonly killTimeoutMs;
56
+ constructor(options: SidecarOptions);
57
+ /**
58
+ * Start the sidecar child process.
59
+ * Idempotent: no-op if already starting or running.
60
+ */
61
+ start(): void;
62
+ /**
63
+ * Stop the sidecar gracefully.
64
+ * Sends SIGTERM, then SIGKILL after killTimeoutMs if still alive.
65
+ */
66
+ stop(): Promise<void>;
67
+ /**
68
+ * Returns current sidecar state snapshot.
69
+ */
70
+ getState(): SidecarState;
71
+ /**
72
+ * Returns last N lines of captured stderr output.
73
+ */
74
+ getStderrLog(lines?: number): string[];
75
+ private _spawn;
76
+ private _handleCrash;
77
+ private _killChild;
78
+ private _appendStderr;
79
+ }
80
+ //# sourceMappingURL=sidecar-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidecar-manager.d.ts","sourceRoot":"","sources":["../../src/desktop/sidecar-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,CAAC;AAExG,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;CAChB;AAYD;;;;;;;;;;GAUG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAO;IAEtC,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;gBAE1B,OAAO,EAAE,cAAc;IAanC;;;OAGG;IACH,KAAK,IAAI,IAAI;IAQb;;;OAGG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAKrB;;OAEG;IACH,QAAQ,IAAI,YAAY;IAWxB;;OAEG;IACH,YAAY,CAAC,KAAK,SAAM,GAAG,MAAM,EAAE;IAMnC,OAAO,CAAC,MAAM;IAoEd,OAAO,CAAC,YAAY;IAsCpB,OAAO,CAAC,UAAU;IA2ClB,OAAO,CAAC,aAAa;CAOtB"}
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ /**
3
+ * Sidecar Manager — manages a Node.js child process sidecar with crash recovery.
4
+ * Detects crashes via process exit event, auto-restarts up to maxCrashes times
5
+ * within a sliding window, and handles port conflicts via auto-increment.
6
+ * Part of #524: Desktop App error handling + local fallback + CLI coexistence.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SidecarManager = void 0;
10
+ const events_1 = require("events");
11
+ const child_process_1 = require("child_process");
12
+ /**
13
+ * SidecarManager events:
14
+ *
15
+ * 'started' → { pid: number | undefined, port: number }
16
+ * 'restarting' → { crashCount: number, reason: string }
17
+ * 'running' → { pid: number | undefined, port: number }
18
+ * 'crashed' → { reason: string, crashCount: number }
19
+ * 'exhausted' → { crashCount: number, message: string }
20
+ * 'port-changed' → { oldPort: number, newPort: number }
21
+ * 'stderr' → { data: string }
22
+ */
23
+ class SidecarManager extends events_1.EventEmitter {
24
+ options;
25
+ child = null;
26
+ status = 'stopped';
27
+ currentPort;
28
+ crashTimestamps = [];
29
+ lastError = null;
30
+ startedAt = null;
31
+ intentionalStop = false;
32
+ stderrLines = [];
33
+ maxStderrLines = 500;
34
+ /** SIGKILL delay after SIGTERM in milliseconds */
35
+ killTimeoutMs = 5000;
36
+ constructor(options) {
37
+ super();
38
+ this.options = {
39
+ command: options.command,
40
+ args: options.args,
41
+ basePort: options.basePort ?? 3100,
42
+ maxCrashes: options.maxCrashes ?? 3,
43
+ crashWindowMs: options.crashWindowMs ?? 300000,
44
+ env: options.env,
45
+ };
46
+ this.currentPort = this.options.basePort;
47
+ }
48
+ /**
49
+ * Start the sidecar child process.
50
+ * Idempotent: no-op if already starting or running.
51
+ */
52
+ start() {
53
+ if (this.status === 'starting' || this.status === 'running') {
54
+ return;
55
+ }
56
+ this.intentionalStop = false;
57
+ this._spawn();
58
+ }
59
+ /**
60
+ * Stop the sidecar gracefully.
61
+ * Sends SIGTERM, then SIGKILL after killTimeoutMs if still alive.
62
+ */
63
+ stop() {
64
+ this.intentionalStop = true;
65
+ return this._killChild();
66
+ }
67
+ /**
68
+ * Returns current sidecar state snapshot.
69
+ */
70
+ getState() {
71
+ return {
72
+ status: this.status,
73
+ pid: this.child?.pid ?? null,
74
+ port: this.currentPort,
75
+ crashCount: this.crashTimestamps.length,
76
+ lastError: this.lastError,
77
+ uptime: this.startedAt !== null ? Date.now() - this.startedAt : 0,
78
+ };
79
+ }
80
+ /**
81
+ * Returns last N lines of captured stderr output.
82
+ */
83
+ getStderrLog(lines = 100) {
84
+ return this.stderrLines.slice(-lines);
85
+ }
86
+ // ─── Private ─────────────────────────────────────────────────────────────────
87
+ _spawn() {
88
+ this.status = 'starting';
89
+ this.startedAt = null;
90
+ const env = {
91
+ ...process.env,
92
+ ...this.options.env,
93
+ PORT: String(this.currentPort),
94
+ };
95
+ let child;
96
+ try {
97
+ child = (0, child_process_1.spawn)(this.options.command, this.options.args, {
98
+ env,
99
+ stdio: ['ignore', 'ignore', 'pipe'],
100
+ });
101
+ }
102
+ catch (err) {
103
+ const reason = err instanceof Error ? err.message : String(err);
104
+ this._handleCrash(reason);
105
+ return;
106
+ }
107
+ // Guard: if spawn returns a non-process object (e.g. in tests when mock is exhausted)
108
+ if (!child || typeof child.on !== 'function') {
109
+ this._handleCrash('spawn returned invalid process');
110
+ return;
111
+ }
112
+ this.child = child;
113
+ this.startedAt = Date.now();
114
+ this.status = 'running';
115
+ // Capture stderr BEFORE emitting events so that listeners attached in tests
116
+ // see a fully-initialized state.
117
+ if (child.stderr) {
118
+ child.stderr.setEncoding('utf8');
119
+ child.stderr.on('data', (data) => {
120
+ this._appendStderr(data);
121
+ this.emit('stderr', { data });
122
+ });
123
+ }
124
+ // Register exit/error handlers before emitting 'started'/'running' so
125
+ // synchronous crashes in tests are handled correctly.
126
+ child.on('exit', (code, signal) => {
127
+ if (this.intentionalStop) {
128
+ this.status = 'stopped';
129
+ this.child = null;
130
+ return;
131
+ }
132
+ const reason = signal
133
+ ? `killed by signal ${signal}`
134
+ : `exited with code ${code ?? 'unknown'}`;
135
+ this._handleCrash(reason);
136
+ });
137
+ child.on('error', (err) => {
138
+ if (!this.intentionalStop) {
139
+ this._handleCrash(err.message);
140
+ }
141
+ });
142
+ this.emit('started', { pid: child.pid, port: this.currentPort });
143
+ this.emit('running', { pid: child.pid, port: this.currentPort });
144
+ }
145
+ _handleCrash(reason) {
146
+ this.lastError = reason;
147
+ this.child = null;
148
+ const now = Date.now();
149
+ // Evict timestamps outside the sliding window
150
+ this.crashTimestamps = this.crashTimestamps.filter((ts) => now - ts < this.options.crashWindowMs);
151
+ this.crashTimestamps.push(now);
152
+ const crashCount = this.crashTimestamps.length;
153
+ this.status = 'crashed';
154
+ console.error(`[SidecarManager] Sidecar crashed (${crashCount}/${this.options.maxCrashes}): ${reason}`);
155
+ this.emit('crashed', { reason, crashCount });
156
+ if (crashCount >= this.options.maxCrashes) {
157
+ this.status = 'exhausted';
158
+ console.error('[SidecarManager] Crash limit reached — giving up');
159
+ this.emit('exhausted', { crashCount, message: 'Server keeps crashing' });
160
+ return;
161
+ }
162
+ // Check if recent stderr suggests port conflict
163
+ const recentStderr = this.stderrLines.slice(-20).join('\n');
164
+ if (recentStderr.includes('EADDRINUSE')) {
165
+ const oldPort = this.currentPort;
166
+ this.currentPort = oldPort + 1;
167
+ console.error(`[SidecarManager] Port ${oldPort} in use, incrementing to ${this.currentPort}`);
168
+ this.emit('port-changed', { oldPort, newPort: this.currentPort });
169
+ }
170
+ this.status = 'restarting';
171
+ this.emit('restarting', { crashCount, reason });
172
+ this._spawn();
173
+ }
174
+ _killChild() {
175
+ const child = this.child;
176
+ this.child = null;
177
+ if (!child || child.exitCode !== null) {
178
+ this.status = 'stopped';
179
+ return Promise.resolve();
180
+ }
181
+ return new Promise((resolve) => {
182
+ let killTimer = null;
183
+ const onExit = () => {
184
+ if (killTimer)
185
+ clearTimeout(killTimer);
186
+ this.status = 'stopped';
187
+ resolve();
188
+ };
189
+ child.once('exit', onExit);
190
+ try {
191
+ child.kill('SIGTERM');
192
+ }
193
+ catch {
194
+ // Already dead — proceed to cleanup
195
+ onExit();
196
+ return;
197
+ }
198
+ killTimer = setTimeout(() => {
199
+ child.removeListener('exit', onExit);
200
+ try {
201
+ child.kill('SIGKILL');
202
+ }
203
+ catch {
204
+ // Already dead
205
+ }
206
+ this.status = 'stopped';
207
+ resolve();
208
+ }, this.killTimeoutMs);
209
+ if (killTimer.unref)
210
+ killTimer.unref();
211
+ });
212
+ }
213
+ _appendStderr(data) {
214
+ const lines = data.split('\n');
215
+ this.stderrLines.push(...lines);
216
+ if (this.stderrLines.length > this.maxStderrLines) {
217
+ this.stderrLines = this.stderrLines.slice(-this.maxStderrLines);
218
+ }
219
+ }
220
+ }
221
+ exports.SidecarManager = SidecarManager;
222
+ //# sourceMappingURL=sidecar-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidecar-manager.js","sourceRoot":"","sources":["../../src/desktop/sidecar-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * TunnelFailureHandler — classifies cloudflared failures, manages retries,
3
+ * and falls back to local-only mode after max retries are exceeded.
4
+ * Part of #524 Error handling + local fallback + CLI coexistence.
5
+ */
6
+ import { EventEmitter } from 'events';
7
+ export type TunnelFailureReason = 'process_crash' | 'network_disconnect' | 'firewall_block' | 'timeout' | 'dns_failure' | 'unknown';
8
+ export type TunnelMode = 'tunnel' | 'local-only' | 'reconnecting';
9
+ export interface TunnelFailureEvent {
10
+ reason: TunnelFailureReason;
11
+ message: string;
12
+ suggestion: string;
13
+ canRetry: boolean;
14
+ timestamp: number;
15
+ }
16
+ export declare class TunnelFailureHandler extends EventEmitter {
17
+ private mode;
18
+ private consecutiveFailures;
19
+ private readonly maxRetries;
20
+ private readonly retryDelayMs;
21
+ private retryTimer;
22
+ /**
23
+ * Detect failure reason from cloudflared stderr output and process exit code.
24
+ */
25
+ classifyFailure(exitCode: number | null, stderr: string): TunnelFailureReason;
26
+ /**
27
+ * Handle a tunnel failure: increment counter, emit events, manage retries.
28
+ * Switches to local-only mode after maxRetries consecutive failures.
29
+ */
30
+ handleFailure(exitCode: number | null, stderr: string): TunnelFailureEvent;
31
+ /**
32
+ * Get plain-language message for a failure reason (no technical jargon).
33
+ */
34
+ private getPlainMessage;
35
+ /**
36
+ * Get actionable suggestion for a failure reason.
37
+ */
38
+ private getSuggestion;
39
+ /**
40
+ * Switch to local-only mode after max retries exceeded.
41
+ */
42
+ private fallbackToLocalOnly;
43
+ /**
44
+ * Reset failure counter and mode when tunnel is successfully restored.
45
+ */
46
+ resetOnSuccess(): void;
47
+ /**
48
+ * Get the current tunnel mode.
49
+ */
50
+ getMode(): TunnelMode;
51
+ /**
52
+ * Clean up timers and remove all listeners.
53
+ */
54
+ destroy(): void;
55
+ }
56
+ //# sourceMappingURL=tunnel-failure-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-failure-handler.d.ts","sourceRoot":"","sources":["../../src/desktop/tunnel-failure-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,MAAM,mBAAmB,GAC3B,eAAe,GACf,oBAAoB,GACpB,gBAAgB,GAChB,SAAS,GACT,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,YAAY,GAAG,cAAc,CAAC;AAElE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,oBAAqB,SAAQ,YAAY;IACpD,OAAO,CAAC,IAAI,CAAwB;IACpC,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,UAAU,CAA+B;IAEjD;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,mBAAmB;IA0B7E;;;OAGG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,kBAAkB;IAmC1E;;OAEG;IACH,OAAO,CAAC,eAAe;IAiBvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAiBrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAa3B;;OAEG;IACH,cAAc,IAAI,IAAI;IAiBtB;;OAEG;IACH,OAAO,IAAI,UAAU;IAIrB;;OAEG;IACH,OAAO,IAAI,IAAI;CAOhB"}
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * TunnelFailureHandler — classifies cloudflared failures, manages retries,
4
+ * and falls back to local-only mode after max retries are exceeded.
5
+ * Part of #524 Error handling + local fallback + CLI coexistence.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.TunnelFailureHandler = void 0;
9
+ const events_1 = require("events");
10
+ class TunnelFailureHandler extends events_1.EventEmitter {
11
+ mode = 'tunnel';
12
+ consecutiveFailures = 0;
13
+ maxRetries = 3;
14
+ retryDelayMs = 10000;
15
+ retryTimer = null;
16
+ /**
17
+ * Detect failure reason from cloudflared stderr output and process exit code.
18
+ */
19
+ classifyFailure(exitCode, stderr) {
20
+ const lower = stderr.toLowerCase();
21
+ if (lower.includes('connection refused') || lower.includes('econnrefused')) {
22
+ return 'network_disconnect';
23
+ }
24
+ if (lower.includes('permission denied') || lower.includes('eacces')) {
25
+ return 'firewall_block';
26
+ }
27
+ if (lower.includes('dns') || lower.includes('resolve')) {
28
+ return 'dns_failure';
29
+ }
30
+ if (exitCode === null) {
31
+ return 'timeout';
32
+ }
33
+ if (exitCode === 1) {
34
+ return 'process_crash';
35
+ }
36
+ return 'unknown';
37
+ }
38
+ /**
39
+ * Handle a tunnel failure: increment counter, emit events, manage retries.
40
+ * Switches to local-only mode after maxRetries consecutive failures.
41
+ */
42
+ handleFailure(exitCode, stderr) {
43
+ const reason = this.classifyFailure(exitCode, stderr);
44
+ this.consecutiveFailures++;
45
+ const event = {
46
+ reason,
47
+ message: this.getPlainMessage(reason),
48
+ suggestion: this.getSuggestion(reason),
49
+ canRetry: this.consecutiveFailures < this.maxRetries,
50
+ timestamp: Date.now(),
51
+ };
52
+ this.emit('tunnel-failure', event);
53
+ if (this.consecutiveFailures < this.maxRetries) {
54
+ this.mode = 'reconnecting';
55
+ this.emit('tunnel-reconnecting', { attempt: this.consecutiveFailures, delayMs: this.retryDelayMs });
56
+ if (this.retryTimer) {
57
+ clearTimeout(this.retryTimer);
58
+ }
59
+ // Backoff delay — callers should listen to 'tunnel-reconnecting' and re-attempt
60
+ // connection after the delay expires. The timer itself only resets state.
61
+ this.retryTimer = setTimeout(() => {
62
+ this.retryTimer = null;
63
+ this.emit('tunnel-retry-ready', { attempt: this.consecutiveFailures });
64
+ }, this.retryDelayMs);
65
+ this.retryTimer.unref();
66
+ }
67
+ else {
68
+ this.fallbackToLocalOnly();
69
+ }
70
+ return event;
71
+ }
72
+ /**
73
+ * Get plain-language message for a failure reason (no technical jargon).
74
+ */
75
+ getPlainMessage(reason) {
76
+ switch (reason) {
77
+ case 'process_crash':
78
+ return 'The secure connection stopped unexpectedly';
79
+ case 'network_disconnect':
80
+ return 'Your internet connection appears to be down';
81
+ case 'firewall_block':
82
+ return 'Your firewall or antivirus may be blocking the connection';
83
+ case 'timeout':
84
+ return 'The connection is taking too long to establish';
85
+ case 'dns_failure':
86
+ return 'Unable to reach the connection service';
87
+ case 'unknown':
88
+ return 'An unexpected connection error occurred';
89
+ }
90
+ }
91
+ /**
92
+ * Get actionable suggestion for a failure reason.
93
+ */
94
+ getSuggestion(reason) {
95
+ switch (reason) {
96
+ case 'process_crash':
97
+ return 'The app will try to reconnect automatically. If this keeps happening, try restarting the app.';
98
+ case 'network_disconnect':
99
+ return 'Check your internet connection and try again.';
100
+ case 'firewall_block':
101
+ return 'Check your firewall or antivirus settings and allow this app, then restart.';
102
+ case 'timeout':
103
+ return 'Check your internet connection. If the problem persists, try restarting the app.';
104
+ case 'dns_failure':
105
+ return 'Check your internet connection or try switching to a different network.';
106
+ case 'unknown':
107
+ return 'Try restarting the app. If the problem continues, check your network settings.';
108
+ }
109
+ }
110
+ /**
111
+ * Switch to local-only mode after max retries exceeded.
112
+ */
113
+ fallbackToLocalOnly() {
114
+ this.mode = 'local-only';
115
+ if (this.retryTimer) {
116
+ clearTimeout(this.retryTimer);
117
+ this.retryTimer = null;
118
+ }
119
+ this.emit('tunnel-local-only', {
120
+ consecutiveFailures: this.consecutiveFailures,
121
+ });
122
+ }
123
+ /**
124
+ * Reset failure counter and mode when tunnel is successfully restored.
125
+ */
126
+ resetOnSuccess() {
127
+ const wasReconnecting = this.mode === 'reconnecting';
128
+ const wasLocalOnly = this.mode === 'local-only';
129
+ this.consecutiveFailures = 0;
130
+ this.mode = 'tunnel';
131
+ if (this.retryTimer) {
132
+ clearTimeout(this.retryTimer);
133
+ this.retryTimer = null;
134
+ }
135
+ if (wasReconnecting || wasLocalOnly) {
136
+ this.emit('tunnel-restored');
137
+ }
138
+ }
139
+ /**
140
+ * Get the current tunnel mode.
141
+ */
142
+ getMode() {
143
+ return this.mode;
144
+ }
145
+ /**
146
+ * Clean up timers and remove all listeners.
147
+ */
148
+ destroy() {
149
+ if (this.retryTimer) {
150
+ clearTimeout(this.retryTimer);
151
+ this.retryTimer = null;
152
+ }
153
+ this.removeAllListeners();
154
+ }
155
+ }
156
+ exports.TunnelFailureHandler = TunnelFailureHandler;
157
+ //# sourceMappingURL=tunnel-failure-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-failure-handler.js","sourceRoot":"","sources":["../../src/desktop/tunnel-failure-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Tunnel Resilience Manager — manages cloudflared tunnel lifecycle with
3
+ * auto-reconnect, blip detection, and local-only fallback.
4
+ * Part of #524 Desktop App: Error handling + local fallback + CLI coexistence.
5
+ */
6
+ import { EventEmitter } from 'events';
7
+ export type TunnelStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'local-only';
8
+ export interface TunnelOptions {
9
+ cloudflaredPath?: string;
10
+ targetPort: number;
11
+ maxReconnectAttempts?: number;
12
+ reconnectIntervalMs?: number;
13
+ blipThresholdMs?: number;
14
+ }
15
+ export interface TunnelState {
16
+ status: TunnelStatus;
17
+ tunnelUrl: string | null;
18
+ reconnectAttempt: number;
19
+ lastError: string | null;
20
+ disconnectedAt: number | null;
21
+ localModeReason: string | null;
22
+ }
23
+ /**
24
+ * TunnelManager manages the cloudflared tunnel process with:
25
+ * - Auto-reconnect on crash (up to maxReconnectAttempts)
26
+ * - Blip detection (transparent for short disconnects)
27
+ * - Local-only fallback when all reconnects fail
28
+ * - Antivirus/permission block detection
29
+ *
30
+ * Events emitted:
31
+ * - 'connected' → { tunnelUrl: string }
32
+ * - 'disconnected' → { reason: string }
33
+ * - 'reconnecting' → { attempt: number, maxAttempts: number }
34
+ * - 'reconnected' → { tunnelUrl: string }
35
+ * - 'local-only' → { reason: string, guidance: string }
36
+ * - 'status-changed' → { oldStatus: TunnelStatus, newStatus: TunnelStatus }
37
+ * - 'blocked' → { reason: string, guidance: string }
38
+ */
39
+ export declare class TunnelManager extends EventEmitter {
40
+ private readonly options;
41
+ private process;
42
+ private state;
43
+ private intentionalStop;
44
+ private reconnectTimer;
45
+ constructor(options: TunnelOptions);
46
+ /**
47
+ * Start the tunnel. Finds cloudflared binary and spawns the process.
48
+ * Resolves once the process is spawned (not once URL is received).
49
+ */
50
+ start(): Promise<void>;
51
+ /**
52
+ * Stop the tunnel gracefully. Does not trigger reconnect.
53
+ */
54
+ stop(): void;
55
+ /**
56
+ * Manual retry from local-only mode.
57
+ */
58
+ retry(): Promise<void>;
59
+ /**
60
+ * Get the current tunnel state snapshot.
61
+ */
62
+ getState(): TunnelState;
63
+ /**
64
+ * Find the cloudflared binary path.
65
+ * Checks known install locations and PATH.
66
+ */
67
+ findCloudflared(): Promise<string | null>;
68
+ private _launchTunnel;
69
+ /**
70
+ * Synchronously spawn the cloudflared process and register all event handlers.
71
+ * This is separated from _launchTunnel so handlers are registered in the same
72
+ * synchronous tick as the spawn call.
73
+ */
74
+ private _spawnProcess;
75
+ private _parseOutput;
76
+ private _handleSpawnError;
77
+ private _handleExit;
78
+ private _scheduleReconnect;
79
+ private _transitionToLocalOnly;
80
+ private _isBlip;
81
+ private _setStatus;
82
+ private _clearReconnectTimer;
83
+ private _runCommand;
84
+ }
85
+ //# sourceMappingURL=tunnel-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-manager.d.ts","sourceRoot":"","sources":["../../src/desktop/tunnel-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAMtC,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,YAAY,CAAC;AAEvG,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,cAAc,CAA+B;gBAEzC,OAAO,EAAE,aAAa;IAmBlC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAUZ;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B;;OAEG;IACH,QAAQ,IAAI,WAAW;IAIvB;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAmDjC,aAAa;IAoB3B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,YAAY;IAoBpB,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,WAAW;CAsBpB"}