termbeam 1.17.7 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/package.json +1 -1
  2. package/public/assets/{_basePickBy-COFR_F6O.js → _basePickBy-CVn0rIeA.js} +1 -1
  3. package/public/assets/{_baseUniq-DsM_tAIh.js → _baseUniq-D-ViDZI1.js} +1 -1
  4. package/public/assets/{arc-CkjHihPP.js → arc-BWX7iih_.js} +1 -1
  5. package/public/assets/{architectureDiagram-2XIMDMQ5-DF0zcOrz.js → architectureDiagram-2XIMDMQ5-Dr3bqfT2.js} +1 -1
  6. package/public/assets/{blockDiagram-WCTKOSBZ-1ZGwRrfn.js → blockDiagram-WCTKOSBZ-RZQh_7bp.js} +1 -1
  7. package/public/assets/{c4Diagram-IC4MRINW-CtFQssnA.js → c4Diagram-IC4MRINW-BM1jg1mo.js} +1 -1
  8. package/public/assets/channel-N7LACYfb.js +1 -0
  9. package/public/assets/{chunk-4BX2VUAB-pAQlkdCs.js → chunk-4BX2VUAB-Cz0Sexay.js} +1 -1
  10. package/public/assets/{chunk-55IACEB6-C1YDnAkF.js → chunk-55IACEB6-DhjD4VKO.js} +1 -1
  11. package/public/assets/{chunk-FMBD7UC4-0fJhTPpT.js → chunk-FMBD7UC4-H_1l13Rz.js} +1 -1
  12. package/public/assets/{chunk-JSJVCQXG-Bv4lt_uR.js → chunk-JSJVCQXG-D_GdzPQn.js} +1 -1
  13. package/public/assets/{chunk-KX2RTZJC-DSuNgcxs.js → chunk-KX2RTZJC-BU-NTTwY.js} +1 -1
  14. package/public/assets/{chunk-NQ4KR5QH-B8qhuc2K.js → chunk-NQ4KR5QH-_X3IQmYu.js} +1 -1
  15. package/public/assets/{chunk-QZHKN3VN-CLJObXzb.js → chunk-QZHKN3VN-CrSItx1v.js} +1 -1
  16. package/public/assets/{chunk-WL4C6EOR-Cty7zCuA.js → chunk-WL4C6EOR-XtpH4KKr.js} +1 -1
  17. package/public/assets/classDiagram-VBA2DB6C-Ct_F3N6M.js +1 -0
  18. package/public/assets/classDiagram-v2-RAHNMMFH-Ct_F3N6M.js +1 -0
  19. package/public/assets/clone-DfaqXUXL.js +1 -0
  20. package/public/assets/{cose-bilkent-S5V4N54A-BCbps77Y.js → cose-bilkent-S5V4N54A-BNYNzK1p.js} +1 -1
  21. package/public/assets/{dagre-KLK3FWXG-BAFtP8kn.js → dagre-KLK3FWXG-CvP5MaIU.js} +1 -1
  22. package/public/assets/{diagram-E7M64L7V-D96MX7Ne.js → diagram-E7M64L7V-C1ypW2kT.js} +1 -1
  23. package/public/assets/{diagram-IFDJBPK2-CWijQtVE.js → diagram-IFDJBPK2-CH5mcu6V.js} +1 -1
  24. package/public/assets/{diagram-P4PSJMXO-DfaT5SNw.js → diagram-P4PSJMXO-D7EqFseu.js} +1 -1
  25. package/public/assets/{erDiagram-INFDFZHY-hBbhb86C.js → erDiagram-INFDFZHY-Dc_91GQC.js} +1 -1
  26. package/public/assets/{flowDiagram-PKNHOUZH-DG_7KLqz.js → flowDiagram-PKNHOUZH-wUw5Mjvb.js} +1 -1
  27. package/public/assets/{ganttDiagram-A5KZAMGK-CgjVNpj-.js → ganttDiagram-A5KZAMGK-C55hiPqb.js} +1 -1
  28. package/public/assets/{gitGraphDiagram-K3NZZRJ6-7K4KlJrp.js → gitGraphDiagram-K3NZZRJ6-D3HBxR11.js} +1 -1
  29. package/public/assets/{graph-BeJIbeQ_.js → graph-mq5DkKwp.js} +1 -1
  30. package/public/assets/{index-BKVhZe-L.js → index-BxVq7AYs.js} +118 -118
  31. package/public/assets/index-Cpm34cTy.css +32 -0
  32. package/public/assets/{infoDiagram-LFFYTUFH-BpW2L8Ym.js → infoDiagram-LFFYTUFH-Cisyfr_w.js} +1 -1
  33. package/public/assets/{ishikawaDiagram-PHBUUO56-DNQ-67go.js → ishikawaDiagram-PHBUUO56-BjaKmPoV.js} +1 -1
  34. package/public/assets/{journeyDiagram-4ABVD52K-DBLcMkFz.js → journeyDiagram-4ABVD52K-BNP3C6Ph.js} +1 -1
  35. package/public/assets/{kanban-definition-K7BYSVSG-CKEe0d57.js → kanban-definition-K7BYSVSG-D9TVNfL2.js} +1 -1
  36. package/public/assets/{layout-CPrQCiWo.js → layout-DIfi_3Cg.js} +1 -1
  37. package/public/assets/{linear-Bvp8GGTr.js → linear-CNFortHj.js} +1 -1
  38. package/public/assets/{mindmap-definition-YRQLILUH-Dh6H4qCD.js → mindmap-definition-YRQLILUH-CfNe7W8X.js} +1 -1
  39. package/public/assets/{pieDiagram-SKSYHLDU-Bhm_CLJm.js → pieDiagram-SKSYHLDU-C3T1FeFD.js} +1 -1
  40. package/public/assets/{quadrantDiagram-337W2JSQ-BPzwxRlL.js → quadrantDiagram-337W2JSQ-Dcj0avLR.js} +1 -1
  41. package/public/assets/{requirementDiagram-Z7DCOOCP-CgbtPhM8.js → requirementDiagram-Z7DCOOCP-D4yTZU0p.js} +1 -1
  42. package/public/assets/{sankeyDiagram-WA2Y5GQK-2IIigF9J.js → sankeyDiagram-WA2Y5GQK-CpT1bDmZ.js} +1 -1
  43. package/public/assets/{sequenceDiagram-2WXFIKYE-C20cCdgm.js → sequenceDiagram-2WXFIKYE-CPsVjHHb.js} +1 -1
  44. package/public/assets/{stateDiagram-RAJIS63D-CGcduF1R.js → stateDiagram-RAJIS63D-CbB0QqSa.js} +1 -1
  45. package/public/assets/stateDiagram-v2-FVOUBMTO-CQnYvVz1.js +1 -0
  46. package/public/assets/{timeline-definition-YZTLITO2-Ck1-gAxD.js → timeline-definition-YZTLITO2-Bi12ioU0.js} +1 -1
  47. package/public/assets/{treemap-KZPCXAKY-CpfBOaD_.js → treemap-KZPCXAKY-Ct-pligZ.js} +1 -1
  48. package/public/assets/{vennDiagram-LZ73GAT5-DXJlsrKL.js → vennDiagram-LZ73GAT5-Ci8AKqD1.js} +1 -1
  49. package/public/assets/{xychartDiagram-JWTSCODW-BFlTfqgI.js → xychartDiagram-JWTSCODW-BsOGuDAL.js} +1 -1
  50. package/public/index.html +2 -2
  51. package/public/sw.js +1 -1
  52. package/src/server/index.js +85 -13
  53. package/src/server/routes.js +77 -0
  54. package/src/tunnel/index.js +202 -55
  55. package/public/assets/channel-Pj6K0-7n.js +0 -1
  56. package/public/assets/classDiagram-VBA2DB6C-CEuggsOe.js +0 -1
  57. package/public/assets/classDiagram-v2-RAHNMMFH-CEuggsOe.js +0 -1
  58. package/public/assets/clone-D5ZwbJNp.js +0 -1
  59. package/public/assets/index-OLhvO-lo.css +0 -32
  60. package/public/assets/stateDiagram-v2-FVOUBMTO-BBxXMU25.js +0 -1
@@ -977,6 +977,83 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager }) {
977
977
  res.json({ ok: true });
978
978
  });
979
979
  }
980
+
981
+ // --- Tunnel token renewal ---
982
+ app.get('/api/tunnel/status', apiRateLimit, auth.middleware, (_req, res) => {
983
+ const tunnelStatus = state.tunnelStatus || { state: 'unknown' };
984
+ // Injected via state to avoid loading the full tunnel module in test contexts
985
+ const getLoginInfo = state.getLoginInfo;
986
+ const loginInfo = getLoginInfo ? getLoginInfo() : null;
987
+ res.json({
988
+ ...tunnelStatus,
989
+ provider: loginInfo?.provider ?? null,
990
+ tokenLifetimeSeconds: loginInfo?.tokenLifetimeSeconds ?? null,
991
+ });
992
+ });
993
+
994
+ app.post('/api/tunnel/renew', apiRateLimit, auth.middleware, (_req, res) => {
995
+ const { spawn } = require('child_process');
996
+ const { findDevtunnel } = require('../tunnel');
997
+ const cmd = findDevtunnel() || 'devtunnel';
998
+ const proc = spawn(cmd, ['user', 'login', '-d'], {
999
+ stdio: ['pipe', 'pipe', 'pipe'],
1000
+ });
1001
+
1002
+ let output = '';
1003
+ let responded = false;
1004
+
1005
+ const timeout = setTimeout(() => {
1006
+ if (!responded) {
1007
+ responded = true;
1008
+ proc.kill();
1009
+ res.status(504).json({ error: 'Timed out waiting for device code' });
1010
+ }
1011
+ }, 15000);
1012
+
1013
+ function tryParse(data) {
1014
+ if (responded || output.length > 10_000) return;
1015
+ output += data;
1016
+ // Entra: "open the page https://... and enter the code ABC123 to authenticate"
1017
+ // GitHub: "Browse to https://... and enter the code: AB12-CD34"
1018
+ const match =
1019
+ output.match(/open the page (https:\/\/[^\s]+) and enter the code ([A-Z0-9]+)/i) ||
1020
+ output.match(/Browse to (https:\/\/[^\s]+) and enter the code:?\s*([A-Z0-9-]+)/i);
1021
+ if (match) {
1022
+ responded = true;
1023
+ clearTimeout(timeout);
1024
+ // Stop reading output — we have what we need
1025
+ proc.stdout.removeAllListeners('data');
1026
+ proc.stderr.removeAllListeners('data');
1027
+ res.json({
1028
+ url: match[1],
1029
+ code: match[2],
1030
+ });
1031
+ }
1032
+ }
1033
+
1034
+ proc.stdout.on('data', (d) => tryParse(d.toString()));
1035
+ proc.stderr.on('data', (d) => tryParse(d.toString()));
1036
+
1037
+ proc.on('close', (code) => {
1038
+ clearTimeout(timeout);
1039
+ if (!responded) {
1040
+ responded = true;
1041
+ if (code === 0) {
1042
+ res.json({ ok: true, message: 'Already authenticated' });
1043
+ } else {
1044
+ res.status(500).json({ error: 'DevTunnel login failed' });
1045
+ }
1046
+ }
1047
+ });
1048
+
1049
+ proc.on('error', (err) => {
1050
+ clearTimeout(timeout);
1051
+ if (!responded) {
1052
+ responded = true;
1053
+ res.status(500).json({ error: err.message });
1054
+ }
1055
+ });
1056
+ });
980
1057
  }
981
1058
 
982
1059
  function cleanupUploadedFiles() {
@@ -21,19 +21,77 @@ let restartAttempts = 0;
21
21
  let isRestarting = false;
22
22
  let restartTimer = null;
23
23
 
24
+ // --- Auth-wait state ---
25
+ let waitingForAuth = false;
26
+ let authCheckInterval = null;
27
+ let expiryWarned = false;
28
+
24
29
  const HEALTH_CHECK_INTERVAL = 30_000; // 30s between checks
25
30
  const HEALTH_CHECK_GRACE = 2; // 2 consecutive failures before restart
26
31
  const MAX_RESTART_ATTEMPTS = 10;
27
32
  const BACKOFF_DELAYS = [1000, 2000, 5000, 10_000, 15_000, 30_000]; // then stays at 30s
33
+ const AUTH_CHECK_INTERVAL = 30_000; // 30s between auth re-checks
34
+ const TOKEN_EXPIRY_WARN_SECONDS = 3600; // warn at 1 hour remaining
35
+
36
+ const AUTH_ERROR_PATTERNS = ['login required', 'not logged in', 'sign in required'];
28
37
 
29
38
  const SAFE_ID_RE = /^[a-zA-Z0-9._-]+$/;
30
39
 
31
40
  const DEVICE_CODE_INITIAL_TIMEOUT = 15000;
32
41
  const DEVICE_CODE_AUTH_TIMEOUT = 120000;
33
42
 
43
+ function isAuthError(message) {
44
+ const lower = (message || '').toLowerCase();
45
+ return AUTH_ERROR_PATTERNS.some((p) => lower.includes(p));
46
+ }
47
+
48
+ function isLoggedIn() {
49
+ try {
50
+ const out = execFileSync(devtunnelCmd, ['user', 'show'], {
51
+ encoding: 'utf-8',
52
+ stdio: ['pipe', 'pipe', 'pipe'],
53
+ timeout: 10_000,
54
+ });
55
+ return out && !out.toLowerCase().includes('not logged in');
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+
61
+ function getLoginInfo() {
62
+ try {
63
+ const out = execFileSync(devtunnelCmd, ['user', 'show', '-v'], {
64
+ encoding: 'utf-8',
65
+ stdio: ['pipe', 'pipe', 'pipe'],
66
+ timeout: 10_000,
67
+ });
68
+ return parseLoginInfo(out);
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ function parseLoginInfo(output) {
75
+ if (!output || output.toLowerCase().includes('not logged in')) return null;
76
+
77
+ let provider = 'unknown';
78
+ if (output.toLowerCase().includes('github')) provider = 'github';
79
+ else if (output.toLowerCase().includes('microsoft')) provider = 'microsoft';
80
+
81
+ // Parse "Token lifetime: H:MM:SS" from verbose output
82
+ let tokenLifetimeSeconds = null;
83
+ const ltMatch = output.match(/Token lifetime:\s*(\d+):(\d+):(\d+)/);
84
+ if (ltMatch) {
85
+ tokenLifetimeSeconds =
86
+ parseInt(ltMatch[1], 10) * 3600 + parseInt(ltMatch[2], 10) * 60 + parseInt(ltMatch[3], 10);
87
+ }
88
+
89
+ return { provider, tokenLifetimeSeconds };
90
+ }
91
+
34
92
  function deviceCodeLogin(cmd) {
35
93
  return new Promise((resolve, reject) => {
36
- const proc = spawn(cmd, ['user', 'login', '-d'], {
94
+ const proc = spawn(cmd, ['user', 'login', '-e', '-d'], {
37
95
  stdio: ['inherit', 'pipe', 'pipe'],
38
96
  });
39
97
 
@@ -152,7 +210,7 @@ let isPersisted = false;
152
210
  // --- Watchdog: health check & auto-restart ---
153
211
 
154
212
  function checkTunnelHealth() {
155
- if (!tunnelId || !tunnelProc || isRestarting) return;
213
+ if (!tunnelId || !tunnelProc || isRestarting || waitingForAuth) return;
156
214
 
157
215
  const abortCtrl = new AbortController();
158
216
  const timer = setTimeout(() => abortCtrl.abort(), 10_000);
@@ -165,6 +223,20 @@ function checkTunnelHealth() {
165
223
  clearTimeout(timer);
166
224
 
167
225
  if (err) {
226
+ // Auth errors are handled separately — no restart countdown
227
+ if (isAuthError(err.message) || isAuthError(err.stderr)) {
228
+ handleAuthExpiration();
229
+ return;
230
+ }
231
+
232
+ // "Tunnel not found" can mean the user's auth expired (CLI can't
233
+ // query the tunnel without valid credentials). Check login status
234
+ // to distinguish from a genuinely deleted tunnel.
235
+ if (!isLoggedIn()) {
236
+ handleAuthExpiration();
237
+ return;
238
+ }
239
+
168
240
  consecutiveFailures++;
169
241
  log.warn(
170
242
  `Tunnel health check error: ${err.message} (${consecutiveFailures}/${HEALTH_CHECK_GRACE})`,
@@ -193,6 +265,9 @@ function checkTunnelHealth() {
193
265
  log.info(`Tunnel health restored (${hostConns} host connection(s))`);
194
266
  }
195
267
  consecutiveFailures = 0;
268
+
269
+ // Check token expiry while tunnel is healthy
270
+ checkTokenExpiry();
196
271
  return;
197
272
  }
198
273
 
@@ -209,6 +284,26 @@ function checkTunnelHealth() {
209
284
  );
210
285
  }
211
286
 
287
+ function checkTokenExpiry() {
288
+ const info = getLoginInfo();
289
+ if (!info || info.tokenLifetimeSeconds === null) return;
290
+
291
+ const remaining = info.tokenLifetimeSeconds;
292
+
293
+ if (remaining <= TOKEN_EXPIRY_WARN_SECONDS && !expiryWarned) {
294
+ expiryWarned = true;
295
+ const minutes = Math.round(remaining / 60);
296
+ log.warn(`DevTunnel token expires in ${minutes}m`);
297
+ tunnelEvents.emit('auth-expiring', {
298
+ expiresIn: remaining * 1000,
299
+ provider: info.provider,
300
+ });
301
+ } else if (remaining > TOKEN_EXPIRY_WARN_SECONDS) {
302
+ // Reset the warning flag when token is refreshed
303
+ expiryWarned = false;
304
+ }
305
+ }
306
+
212
307
  function startHealthCheck() {
213
308
  stopHealthCheck();
214
309
  consecutiveFailures = 0;
@@ -226,30 +321,74 @@ function stopHealthCheck() {
226
321
  function handleTunnelFailure() {
227
322
  if (isRestarting) return;
228
323
  stopHealthCheck();
324
+ killTunnelProc();
229
325
 
230
- // Kill the zombie process
231
- if (tunnelProc) {
232
- try {
233
- if (process.platform === 'win32' && tunnelProc.pid) {
234
- try {
235
- execFileSync('taskkill', ['/pid', String(tunnelProc.pid), '/T', '/F'], {
236
- stdio: 'pipe',
237
- timeout: 5000,
238
- });
239
- } catch {
240
- /* best effort */
241
- }
242
- } else {
243
- tunnelProc.kill('SIGKILL');
326
+ tunnelEvents.emit('disconnected');
327
+ scheduleRestart();
328
+ }
329
+
330
+ // --- Auth-expiration handling ---
331
+
332
+ function killTunnelProc() {
333
+ if (!tunnelProc) return;
334
+ try {
335
+ if (process.platform === 'win32' && tunnelProc.pid) {
336
+ try {
337
+ execFileSync('taskkill', ['/pid', String(tunnelProc.pid), '/T', '/F'], {
338
+ stdio: 'pipe',
339
+ timeout: 5000,
340
+ });
341
+ } catch {
342
+ /* best effort */
244
343
  }
245
- } catch {
246
- /* best effort */
344
+ } else {
345
+ tunnelProc.kill('SIGKILL');
247
346
  }
248
- tunnelProc = null;
347
+ } catch {
348
+ /* best effort */
249
349
  }
350
+ tunnelProc = null;
351
+ }
352
+
353
+ function handleAuthExpiration() {
354
+ if (waitingForAuth) return;
355
+ stopHealthCheck();
356
+ killTunnelProc();
250
357
 
251
358
  tunnelEvents.emit('disconnected');
252
- scheduleRestart();
359
+ tunnelEvents.emit('auth-expired');
360
+ startAuthWait();
361
+ }
362
+
363
+ function startAuthWait() {
364
+ if (waitingForAuth) return;
365
+ waitingForAuth = true;
366
+ isRestarting = false;
367
+ restartAttempts = 0;
368
+ consecutiveFailures = 0;
369
+
370
+ log.warn('DevTunnel auth token expired (Microsoft tokens expire after a few days).');
371
+ log.warn('Tunnel is paused — re-authenticate on the host machine to restore:');
372
+ log.warn(' devtunnel user login -d');
373
+ log.warn('Tunnel will auto-reconnect once auth is restored.');
374
+
375
+ authCheckInterval = setInterval(() => {
376
+ if (isLoggedIn()) {
377
+ log.info('DevTunnel auth restored — resuming tunnel');
378
+ stopAuthWait();
379
+ tunnelEvents.emit('auth-restored');
380
+ scheduleRestart();
381
+ }
382
+ }, AUTH_CHECK_INTERVAL);
383
+ authCheckInterval.unref();
384
+ }
385
+
386
+ function stopAuthWait() {
387
+ waitingForAuth = false;
388
+ if (authCheckInterval) {
389
+ clearInterval(authCheckInterval);
390
+ authCheckInterval = null;
391
+ }
253
392
  }
254
393
 
255
394
  function scheduleRestart() {
@@ -271,6 +410,15 @@ function scheduleRestart() {
271
410
 
272
411
  restartTimer = setTimeout(async () => {
273
412
  restartTimer = null;
413
+
414
+ // If auth expired since restart was scheduled, switch to auth-wait mode
415
+ if (!isLoggedIn()) {
416
+ log.warn('DevTunnel auth expired during restart — waiting for re-authentication');
417
+ isRestarting = false;
418
+ handleAuthExpiration();
419
+ return;
420
+ }
421
+
274
422
  try {
275
423
  const result = await hostTunnel();
276
424
  if (result) {
@@ -368,21 +516,30 @@ async function startTunnel(port, options = {}) {
368
516
 
369
517
  log.info('Starting devtunnel...');
370
518
  try {
371
- // Ensure user is logged in
372
- let loggedIn = false;
373
- try {
374
- const userOut = execFileSync(devtunnelCmd, ['user', 'show'], {
375
- encoding: 'utf-8',
376
- stdio: ['pipe', 'pipe', 'pipe'],
377
- });
378
- // user show can succeed but show "not logged in" status
379
- loggedIn = userOut && !userOut.toLowerCase().includes('not logged in');
380
- } catch {}
519
+ // Ensure user is logged in. Prefer Entra over GitHub — Entra tokens auto-refresh
520
+ // for weeks via MSAL, while GitHub tokens expire after 8 hours.
521
+ let loggedIn = isLoggedIn();
522
+ const loginInfo = loggedIn ? getLoginInfo() : null;
523
+
524
+ if (loggedIn && loginInfo) {
525
+ const { provider, tokenLifetimeSeconds } = loginInfo;
526
+ if (provider === 'github') {
527
+ log.warn(
528
+ 'Logged in with GitHub — tokens expire every 8 hours. ' +
529
+ 'For longer sessions, use: devtunnel user login -e -d',
530
+ );
531
+ }
532
+ if (tokenLifetimeSeconds !== null) {
533
+ const h = Math.floor(tokenLifetimeSeconds / 3600);
534
+ const m = Math.round((tokenLifetimeSeconds % 3600) / 60);
535
+ log.info(`DevTunnel token expires in ${h}h ${m}m`);
536
+ }
537
+ }
381
538
 
382
539
  if (!loggedIn) {
383
- log.info('devtunnel not logged in, launching browser login (30s timeout)...');
540
+ log.info('Logging in to DevTunnel with Microsoft Entra (recommended for long sessions)...');
384
541
  try {
385
- execFileSync(devtunnelCmd, ['user', 'login'], { stdio: 'inherit', timeout: 30000 });
542
+ execFileSync(devtunnelCmd, ['user', 'login', '-e'], { stdio: 'inherit', timeout: 30000 });
386
543
  } catch {
387
544
  log.info('Browser login failed or unavailable, falling back to device code flow...');
388
545
  log.info('A code will be displayed — open the URL on any device to authenticate.');
@@ -391,7 +548,7 @@ async function startTunnel(port, options = {}) {
391
548
  } catch (_loginErr) {
392
549
  log.error('');
393
550
  log.error(' DevTunnel login failed. To use tunnels, run:');
394
- log.error(' devtunnel user login');
551
+ log.error(' devtunnel user login -e -d');
395
552
  log.error('');
396
553
  log.error(' Or start without a tunnel:');
397
554
  log.error(' termbeam --no-tunnel');
@@ -480,8 +637,9 @@ async function startTunnel(port, options = {}) {
480
637
  }
481
638
 
482
639
  function cleanupTunnel() {
483
- // Stop watchdog first to prevent restart during cleanup
640
+ // Stop watchdog and auth-wait to prevent restart during cleanup
484
641
  stopHealthCheck();
642
+ stopAuthWait();
485
643
  isRestarting = true; // prevent exit handler from restarting
486
644
  if (restartTimer) {
487
645
  clearTimeout(restartTimer);
@@ -489,26 +647,7 @@ function cleanupTunnel() {
489
647
  }
490
648
 
491
649
  const id = tunnelId;
492
- if (tunnelProc) {
493
- try {
494
- // On Windows, kill the process tree to ensure all children die
495
- if (process.platform === 'win32' && tunnelProc.pid) {
496
- try {
497
- execFileSync('taskkill', ['/pid', String(tunnelProc.pid), '/T', '/F'], {
498
- stdio: 'pipe',
499
- timeout: 5000,
500
- });
501
- } catch {
502
- /* best effort */
503
- }
504
- } else {
505
- tunnelProc.kill('SIGKILL');
506
- }
507
- } catch {
508
- /* best effort */
509
- }
510
- tunnelProc = null;
511
- }
650
+ killTunnelProc();
512
651
  if (id) {
513
652
  tunnelId = null;
514
653
  if (isPersisted) {
@@ -527,6 +666,14 @@ function cleanupTunnel() {
527
666
  consecutiveFailures = 0;
528
667
  restartAttempts = 0;
529
668
  isRestarting = false;
669
+ expiryWarned = false;
530
670
  }
531
671
 
532
- module.exports = { startTunnel, cleanupTunnel, findDevtunnel, tunnelEvents };
672
+ module.exports = {
673
+ startTunnel,
674
+ cleanupTunnel,
675
+ findDevtunnel,
676
+ tunnelEvents,
677
+ getLoginInfo,
678
+ parseLoginInfo,
679
+ };
@@ -1 +0,0 @@
1
- import{aq as o,ar as n}from"./index-BKVhZe-L.js";const t=(r,a)=>o.lang.round(n.parse(r)[a]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-WL4C6EOR-Cty7zCuA.js";import{_ as i}from"./index-BKVhZe-L.js";import"./chunk-FMBD7UC4-0fJhTPpT.js";import"./chunk-JSJVCQXG-Bv4lt_uR.js";import"./chunk-55IACEB6-C1YDnAkF.js";import"./chunk-KX2RTZJC-DSuNgcxs.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-WL4C6EOR-Cty7zCuA.js";import{_ as i}from"./index-BKVhZe-L.js";import"./chunk-FMBD7UC4-0fJhTPpT.js";import"./chunk-JSJVCQXG-Bv4lt_uR.js";import"./chunk-55IACEB6-C1YDnAkF.js";import"./chunk-KX2RTZJC-DSuNgcxs.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
1
- import{b as r}from"./_baseUniq-DsM_tAIh.js";var e=4;function a(o){return r(o,e)}export{a as c};