@mmmbuto/nexuscrew 0.8.20 → 0.8.21

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.
@@ -11,7 +11,7 @@
11
11
  <meta name="apple-mobile-web-app-title" content="NexusCrew" />
12
12
  <link rel="manifest" href="/manifest.json" />
13
13
  <title>NexusCrew</title>
14
- <script type="module" crossorigin src="/assets/index-CQnOyaXz.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-DaFQbMHq.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="/assets/index-DQbVJLji.css">
16
16
  </head>
17
17
  <body>
@@ -1 +1 @@
1
- {"version":"0.8.20"}
1
+ {"version":"0.8.21"}
@@ -186,6 +186,14 @@ function openTunnelLog(home, name) {
186
186
  function classifySshFailure(text, remotePort) {
187
187
  const s = String(text || '');
188
188
  const target = store.isPort(remotePort) ? `127.0.0.1:${remotePort}` : 'la porta NexusCrew richiesta';
189
+ const reverseMatch = s.match(/remote port forwarding failed for listen port\s+(\d+)/i)
190
+ || s.match(/(?:bind|listen)[^\r\n]*127\.0\.0\.1:(\d+)/i);
191
+ const reverseListenPort = reverseMatch ? Number(reverseMatch[1]) : null;
192
+ const reverseTarget = store.isPort(reverseListenPort)
193
+ ? `127.0.0.1:${reverseListenPort}` : 'la porta reverse negoziata';
194
+ const reversePolicy = store.isPort(reverseListenPort)
195
+ ? `che la chiave SSH autorizzi permitlisten="${reverseTarget}"`
196
+ : 'che la chiave SSH autorizzi il reverse listen negoziato';
189
197
  if (/administratively prohibited|request (?:was )?denied|open failed:.*prohibited|port forwarding.*(?:disabled|denied)/i.test(s)) {
190
198
  return {
191
199
  code: 'forward-denied',
@@ -219,7 +227,7 @@ function classifySshFailure(text, remotePort) {
219
227
  return {
220
228
  code: 'reverse-forward-failed',
221
229
  detail: 'il canale inverso non è stato aperto dal nodo hub',
222
- hint: 'la porta può essere già occupata oppure il server può vietare il forwarding; verifica listener e policy SSH sul nodo hub',
230
+ hint: `verifica che ${reverseTarget} non sia occupata e ${reversePolicy}; controlla anche AllowTcpForwarding sul nodo hub`,
223
231
  };
224
232
  }
225
233
  if (/Could not request local forwarding|Address already in use/i.test(s)) {
@@ -530,18 +530,16 @@ function settingsRoutes(deps = {}) {
530
530
  let persistedDesired = wasShared;
531
531
  const fetchImpl = seams.fetchImpl || fetch;
532
532
  const notifyHub = (shared) => notifyHubShare({ node, shared, fetchImpl, timeoutMs: 5000 });
533
- const applyLocal = async (shared) => {
534
- st = nodesStore.loadStoreStrict(nodesPath);
535
- st = nodesStore.updateNode(st, name, { shared });
536
- nodesStore.atomicWriteStore(nodesPath, st);
537
- persistedDesired = shared;
538
- node = nodesStore.getNode(st, name);
539
- nodesTunnel.stopTunnel({ home, name });
533
+ const ensureLocal = async ({ restart = false } = {}) => {
534
+ if (restart) nodesTunnel.stopTunnel({ home, name });
540
535
  const started = nodesTunnel.startForward({
541
536
  home, node, localAppPort: runtimePort(),
542
537
  spawnImpl: seams.spawnImpl, spawnSyncImpl: seams.spawnSyncImpl,
543
538
  sshBin: seams.sshBin, logFd: seams.logFd,
544
539
  });
540
+ // startForward is spec-aware: an exact live argv is idempotent, while a
541
+ // stale pre-upgrade supervisor whose -L/-R no longer matches nodes.json
542
+ // is replaced. This is the recovery path for store=false + stale -R.
545
543
  if (!started.started && started.reason !== 'already running') {
546
544
  throw new Error(started.reason || 'avvio SSH fallito');
547
545
  }
@@ -552,12 +550,28 @@ function settingsRoutes(deps = {}) {
552
550
  });
553
551
  if (!ready || ready.status !== 'healthy') {
554
552
  const diagnosis = (seams.readTunnelDiagnostic || nodesTunnel.readTunnelDiagnostic)(home, name, node.remotePort);
555
- throw new Error((diagnosis && diagnosis.detail) || (ready && ready.detail) || 'hub non raggiungibile dopo il riavvio SSH');
553
+ const failure = new Error((diagnosis && diagnosis.detail) || (ready && ready.detail) || 'hub non raggiungibile dopo il riavvio SSH');
554
+ if (diagnosis && typeof diagnosis.hint === 'string') failure.hint = diagnosis.hint;
555
+ throw failure;
556
556
  }
557
+ return started;
558
+ };
559
+ const applyLocal = async (shared) => {
560
+ st = nodesStore.loadStoreStrict(nodesPath);
561
+ st = nodesStore.updateNode(st, name, { shared });
562
+ nodesStore.atomicWriteStore(nodesPath, st);
563
+ persistedDesired = shared;
564
+ node = nodesStore.getNode(st, name);
565
+ await ensureLocal({ restart: true });
557
566
  };
558
567
 
559
568
  try {
560
569
  if (node.shared === body.shared) {
570
+ // Persisted intent is not proof of the detached process mode. A stale
571
+ // supervisor can survive an npm upgrade and keep retrying -R even when
572
+ // the store/UI says private. Re-enter the spec-aware start path before
573
+ // reconciling the hub, without rewriting nodes.json.
574
+ await ensureLocal();
561
575
  await notifyHub(body.shared);
562
576
  return send(res, 200, { name, shared: body.shared, unchanged: true, reconciled: true });
563
577
  }
@@ -578,11 +592,13 @@ function settingsRoutes(deps = {}) {
578
592
  try { await applyLocal(false); } catch (_) { /* best-effort safe rollback */ }
579
593
  }
580
594
  const offPersisted = body.shared === false && persistedDesired === false;
595
+ const redact = (value) => String(value || '').replace(/Bearer\s+\S+/gi, 'Bearer ***');
581
596
  return send(res, 502, {
582
597
  error: body.shared ? 'Share non attivato'
583
598
  : offPersisted ? 'Share disattivato localmente; hub non riconciliato' : 'Share non disattivato',
584
599
  ...(offPersisted ? { shared: false, reconcilePending: true } : {}),
585
- detail: String(e && e.message || e).replace(/Bearer\s+\S+/gi, 'Bearer ***'),
600
+ detail: redact(e && e.message || e),
601
+ ...(e && typeof e.hint === 'string' ? { hint: redact(e.hint) } : {}),
586
602
  });
587
603
  }
588
604
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscrew",
3
- "version": "0.8.20",
3
+ "version": "0.8.21",
4
4
  "description": "Faithful browser tmux client — attach to live sessions over a real PTY, localhost-only, mobile-easy",
5
5
  "main": "lib/server.js",
6
6
  "bin": {