@agentprojectcontext/apx 1.77.1 → 1.77.3

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.
@@ -18,7 +18,7 @@
18
18
  <link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
19
19
  <link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
20
20
  <link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
21
- <script type="module" crossorigin src="/assets/index-BWWpTTSd.js"></script>
21
+ <script type="module" crossorigin src="/assets/index-BptlbKjU.js"></script>
22
22
  <link rel="stylesheet" crossorigin href="/assets/index-D_EJEA1n.css">
23
23
  </head>
24
24
  <body class="bg-background text-foreground antialiased">
@@ -30,6 +30,19 @@ export type AuthState = Status & { reload: () => void };
30
30
  * the panel is opened over the LAN or a tunnel there's no auto-token: the
31
31
  * operator must pair the browser (see PairingScreen).
32
32
  */
33
+ // One in-flight confirm per pairing id. The daemon burns the nonce on first
34
+ // use, so a duplicate call would answer 409 and undo an otherwise successful
35
+ // pairing.
36
+ const inFlightConfirm = new Map<string, ReturnType<typeof Pair.confirm>>();
37
+
38
+ function confirmOnce(pairingId: string) {
39
+ const existing = inFlightConfirm.get(pairingId);
40
+ if (existing) return existing;
41
+ const p = Pair.confirm({ pairing_id: pairingId, label: deviceLabel(), kind: "web" });
42
+ inFlightConfirm.set(pairingId, p);
43
+ return p;
44
+ }
45
+
33
46
  export function useTokenBootstrap(): AuthState {
34
47
  const [state, setState] = useState<Status>({ status: "loading" });
35
48
  const [nonce, setNonce] = useState(0);
@@ -57,15 +70,25 @@ export function useTokenBootstrap(): AuthState {
57
70
  const params = new URLSearchParams(hash);
58
71
  const pairId = params.get("pair");
59
72
  if (pairId) {
60
- history.replaceState(null, "", window.location.pathname + window.location.search);
61
73
  try {
62
- const res = await Pair.confirm({ pairing_id: pairId, label: deviceLabel(), kind: "web" });
74
+ // The nonce is ONE-SHOT on the daemon, and StrictMode runs this
75
+ // effect twice in development — so the second run must reuse the
76
+ // first call's promise instead of confirming again and getting
77
+ // "already confirmed" back. Keyed by id, module-level, because the
78
+ // component remounts between the two runs.
79
+ const res = await confirmOnce(pairId);
63
80
  setToken(res.token);
64
81
  try { localStorage.setItem(STORAGE.token, res.token); } catch { /* quota */ }
65
- if (!cancelled) setState({ status: "ok" });
82
+ // Only NOW drop the fragment. Stripping it first meant a failed
83
+ // confirm left the device with no way to retry: the nonce was spent
84
+ // and the link that carried it was already gone from the URL.
85
+ history.replaceState(null, "", window.location.pathname + window.location.search);
86
+ setState({ status: "ok" });
66
87
  return;
67
88
  } catch {
68
- // Expired/used nonce fall through to the manual pairing screen.
89
+ // Genuinely expired or already spent. Keep the fragment so a reload
90
+ // reports the same thing instead of silently showing a blank
91
+ // pairing screen, and fall through to the manual path.
69
92
  }
70
93
  }
71
94