@djangocfg/api 2.1.619 → 2.1.621

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.
package/dist/auth.mjs CHANGED
@@ -15,6 +15,141 @@ var AUTH_CONSTANTS = {
15
15
  RESEND_COOLDOWN_SECONDS: 30
16
16
  };
17
17
 
18
+ // src/auth/trace.ts
19
+ var KEY = "cfg.auth.trace";
20
+ var MAX = 200;
21
+ var MAX_STRING = 120;
22
+ var SENSITIVE = /token|otp|code|email|password|secret|cookie|authorization|key|identifier/i;
23
+ var TAB = Math.random().toString(36).slice(2, 6);
24
+ var hasWindow = typeof window !== "undefined";
25
+ var memory = [];
26
+ var loaded = false;
27
+ function backend() {
28
+ if (!hasWindow) return null;
29
+ for (const name of ["localStorage", "sessionStorage"]) {
30
+ try {
31
+ const s = window[name];
32
+ s.setItem("__cfg_probe", "1");
33
+ s.removeItem("__cfg_probe");
34
+ return s;
35
+ } catch {
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ __name(backend, "backend");
41
+ function load() {
42
+ if (loaded) return memory;
43
+ loaded = true;
44
+ try {
45
+ const raw = backend()?.getItem(KEY);
46
+ const parsed = raw ? JSON.parse(raw) : [];
47
+ if (Array.isArray(parsed)) memory = parsed.filter((e) => typeof e === "object" && e !== null);
48
+ } catch {
49
+ memory = [];
50
+ }
51
+ return memory;
52
+ }
53
+ __name(load, "load");
54
+ function persist() {
55
+ try {
56
+ backend()?.setItem(KEY, JSON.stringify(memory));
57
+ } catch {
58
+ }
59
+ }
60
+ __name(persist, "persist");
61
+ function clean(fields) {
62
+ if (!fields) return void 0;
63
+ const out = {};
64
+ for (const [k2, v] of Object.entries(fields)) {
65
+ if (SENSITIVE.test(k2)) continue;
66
+ if (typeof v === "string") out[k2] = v.length > MAX_STRING ? `${v.slice(0, MAX_STRING)}\u2026` : v;
67
+ else if (typeof v === "number" || typeof v === "boolean" || v === null) out[k2] = v;
68
+ }
69
+ return Object.keys(out).length ? out : void 0;
70
+ }
71
+ __name(clean, "clean");
72
+ function record(at, ev, fields) {
73
+ const entries = load();
74
+ const p = hasWindow && typeof performance !== "undefined" ? Math.round(performance.now()) : 0;
75
+ entries.push({ t: at, p, tab: TAB, ev, f: clean(fields) });
76
+ if (entries.length > MAX) entries.splice(0, entries.length - MAX);
77
+ persist();
78
+ }
79
+ __name(record, "record");
80
+ function authTrace(ev, fields) {
81
+ if (!hasWindow) return;
82
+ try {
83
+ record(Date.now(), ev, fields);
84
+ } catch {
85
+ }
86
+ }
87
+ __name(authTrace, "authTrace");
88
+ function readAuthTrace() {
89
+ return load().slice();
90
+ }
91
+ __name(readAuthTrace, "readAuthTrace");
92
+ function clearAuthTrace() {
93
+ memory = [];
94
+ loaded = true;
95
+ try {
96
+ backend()?.removeItem(KEY);
97
+ } catch {
98
+ }
99
+ }
100
+ __name(clearAuthTrace, "clearAuthTrace");
101
+ function context() {
102
+ if (!hasWindow) return [];
103
+ const lines = [`url: ${window.location.href.split("?")[0]}`, `online: ${navigator.onLine}`, `ua: ${navigator.userAgent}`];
104
+ try {
105
+ const auth2 = globalThis.__cfgAuthNow;
106
+ const now = auth2?.();
107
+ if (now) lines.push(`now: ${Object.entries(now).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}`);
108
+ } catch {
109
+ }
110
+ return lines;
111
+ }
112
+ __name(context, "context");
113
+ function dumpAuthTrace() {
114
+ const entries = load();
115
+ const first = entries[0]?.t ?? Date.now();
116
+ const rows = entries.map((e) => {
117
+ const fields = e.f ? ` ${Object.entries(e.f).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}` : "";
118
+ const at = new Date(e.t).toISOString().slice(11, 23);
119
+ return `${at} +${String(e.t - first).padStart(6)}ms [${e.tab} p${e.p}] ${e.ev}${fields}`;
120
+ });
121
+ return [`# cfg auth trace, ${entries.length} events`, ...context(), "", ...rows].join("\n");
122
+ }
123
+ __name(dumpAuthTrace, "dumpAuthTrace");
124
+ function installAuthTrace() {
125
+ if (!hasWindow) return;
126
+ const g = globalThis;
127
+ g.__cfgAuthTrace = authTrace;
128
+ g.__cfgAuth = {
129
+ trace: readAuthTrace,
130
+ dump: /* @__PURE__ */ __name(() => {
131
+ const text = dumpAuthTrace();
132
+ console.log(text);
133
+ return text;
134
+ }, "dump"),
135
+ clear: clearAuthTrace
136
+ };
137
+ const queued = g.__cfgAuthTraceQ;
138
+ if (Array.isArray(queued)) {
139
+ g.__cfgAuthTraceQ = [];
140
+ for (const item of queued) {
141
+ if (Array.isArray(item) && typeof item[1] === "string") {
142
+ try {
143
+ record(Number(item[0]) || Date.now(), item[1], item[2]);
144
+ } catch {
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ __name(installAuthTrace, "installAuthTrace");
151
+ installAuthTrace();
152
+
18
153
  // src/auth/context/AuthContext.tsx
19
154
  import { usePathname as usePathname3 } from "next/navigation";
20
155
  import {
@@ -131,10 +266,10 @@ function normalizeSegment(raw) {
131
266
  return raw.trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
132
267
  }
133
268
  __name(normalizeSegment, "normalizeSegment");
134
- var hasWindow = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
269
+ var hasWindow2 = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
135
270
  var domainByHost = /* @__PURE__ */ new Map();
136
271
  function detectCookieDomain() {
137
- if (!hasWindow() || typeof document === "undefined") return null;
272
+ if (!hasWindow2() || typeof document === "undefined") return null;
138
273
  const host = window.location.hostname.toLowerCase();
139
274
  if (!host || !host.includes(".") || host.includes(":") || /^[0-9.]+$/.test(host)) return null;
140
275
  const cached = domainByHost.get(host);
@@ -158,7 +293,7 @@ function detectCookieDomain() {
158
293
  }
159
294
  __name(detectCookieDomain, "detectCookieDomain");
160
295
  function nativeStorage(store) {
161
- if (!hasWindow()) return null;
296
+ if (!hasWindow2()) return null;
162
297
  try {
163
298
  return store === "session" ? window.sessionStorage : window.localStorage;
164
299
  } catch {
@@ -167,7 +302,7 @@ function nativeStorage(store) {
167
302
  }
168
303
  __name(nativeStorage, "nativeStorage");
169
304
  function cookieNames() {
170
- if (!hasWindow()) return [];
305
+ if (!hasWindow2()) return [];
171
306
  try {
172
307
  return document.cookie.split("; ").map((part) => decodeURIComponent(part.split("=")[0] ?? "")).filter(Boolean);
173
308
  } catch {
@@ -192,7 +327,7 @@ function createStorage(owner, options = {}) {
192
327
  const families = Object.values(options.families ?? {});
193
328
  const specByKey = /* @__PURE__ */ new Map();
194
329
  for (const spec of specs) specByKey.set(spec.key, [...specByKey.get(spec.key) ?? [], spec]);
195
- const memory = {
330
+ const memory2 = {
196
331
  local: /* @__PURE__ */ new Map(),
197
332
  session: /* @__PURE__ */ new Map(),
198
333
  cookie: /* @__PURE__ */ new Map()
@@ -206,7 +341,7 @@ function createStorage(owner, options = {}) {
206
341
  if (!key.startsWith(prefix)) throw new TypeError(`storage key "${key}" is not owned by "${owner}"`);
207
342
  }, "assertOwned");
208
343
  function rawGet(store, key) {
209
- if (!hasWindow()) return null;
344
+ if (!hasWindow2()) return null;
210
345
  if (store === "cookie") {
211
346
  try {
212
347
  const escaped = encodeURIComponent(key).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -214,18 +349,18 @@ function createStorage(owner, options = {}) {
214
349
  if (match?.[1] !== void 0) return decodeURIComponent(match[1]);
215
350
  } catch {
216
351
  }
217
- return memory.cookie.get(key) ?? null;
352
+ return memory2.cookie.get(key) ?? null;
218
353
  }
219
354
  try {
220
355
  const value = nativeStorage(store)?.getItem(key) ?? null;
221
356
  if (value !== null) return value;
222
357
  } catch {
223
358
  }
224
- return memory[store].get(key) ?? null;
359
+ return memory2[store].get(key) ?? null;
225
360
  }
226
361
  __name(rawGet, "rawGet");
227
362
  function rawSet(store, key, value, maxAgeSeconds) {
228
- if (!hasWindow()) return false;
363
+ if (!hasWindow2()) return false;
229
364
  try {
230
365
  if (store === "cookie") {
231
366
  const secure = window.location.protocol === "https:" ? "; Secure" : "";
@@ -233,24 +368,24 @@ function createStorage(owner, options = {}) {
233
368
  const domainPart = domain ? `; Domain=${domain}` : "";
234
369
  document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; Path=/; Max-Age=${Math.max(1, Math.floor(maxAgeSeconds ?? cookieMaxAge))}; SameSite=Lax${domainPart}${secure}`;
235
370
  if (domain) expireCookie(key, null);
236
- if (rawGet("cookie", key) === value && !memory.cookie.has(key)) return true;
237
- memory.cookie.set(key, value);
371
+ if (rawGet("cookie", key) === value && !memory2.cookie.has(key)) return true;
372
+ memory2.cookie.set(key, value);
238
373
  return false;
239
374
  }
240
375
  const native = nativeStorage(store);
241
376
  if (!native) throw new Error("storage unavailable");
242
377
  native.setItem(key, value);
243
- memory[store].delete(key);
378
+ memory2[store].delete(key);
244
379
  return true;
245
380
  } catch {
246
- memory[store].set(key, value);
381
+ memory2[store].set(key, value);
247
382
  return false;
248
383
  }
249
384
  }
250
385
  __name(rawSet, "rawSet");
251
386
  function rawRemove(store, key) {
252
- memory[store].delete(key);
253
- if (!hasWindow()) return;
387
+ memory2[store].delete(key);
388
+ if (!hasWindow2()) return;
254
389
  try {
255
390
  if (store === "cookie") {
256
391
  expireCookie(key, null);
@@ -279,7 +414,7 @@ function createStorage(owner, options = {}) {
279
414
  }
280
415
  } catch {
281
416
  }
282
- for (const name of memory[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
417
+ for (const name of memory2[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
283
418
  return out;
284
419
  }
285
420
  __name(listKeys, "listKeys");
@@ -351,7 +486,7 @@ function createStorage(owner, options = {}) {
351
486
  cookieDomain: resolveCookieDomain,
352
487
  onChange(key, listener, store = "local") {
353
488
  assertValid(key);
354
- if (!hasWindow()) return () => {
489
+ if (!hasWindow2()) return () => {
355
490
  };
356
491
  const handler = /* @__PURE__ */ __name((event) => {
357
492
  if (event.storageArea !== nativeStorage(store)) return;
@@ -375,6 +510,29 @@ var REFRESH_KEY = "cfg.auth.refresh_token";
375
510
  var API_KEY_KEY = "cfg.auth.api_key";
376
511
  var storage = true ? cfgStorage : createStorage2("cfg");
377
512
  var isBrowser = typeof window !== "undefined";
513
+ function trace(ev, fields) {
514
+ try {
515
+ const g = globalThis;
516
+ const sink2 = g.__cfgAuthTrace;
517
+ if (sink2) {
518
+ sink2(ev, fields);
519
+ return;
520
+ }
521
+ const queue = g.__cfgAuthTraceQ ?? [];
522
+ g.__cfgAuthTraceQ = queue;
523
+ if (queue.length < 100) queue.push([Date.now(), ev, fields]);
524
+ } catch {
525
+ }
526
+ }
527
+ __name(trace, "trace");
528
+ function pathOf(url) {
529
+ try {
530
+ return new URL(url, "http://x").pathname;
531
+ } catch {
532
+ return "";
533
+ }
534
+ }
535
+ __name(pathOf, "pathOf");
378
536
  function secondsUntilExp(token) {
379
537
  const exp = jwtExpMs(token);
380
538
  return exp === null ? void 0 : Math.max(1, Math.floor((exp - Date.now()) / 1e3));
@@ -534,7 +692,10 @@ __name(scheduleExpiryFlip, "scheduleExpiryFlip");
534
692
  function notifySessionChanged() {
535
693
  const next = computeSnapshot();
536
694
  const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
537
- if (changed) _snapshot = next;
695
+ if (changed) {
696
+ _snapshot = next;
697
+ trace("store.snapshot", { status: next.status });
698
+ }
538
699
  scheduleExpiryFlip();
539
700
  if (!changed) return;
540
701
  for (const listener of Array.from(_sessionListeners)) {
@@ -617,6 +778,7 @@ var auth = {
617
778
  return _storage.get(ACCESS_KEY);
618
779
  },
619
780
  setToken(token) {
781
+ trace("store.setToken", { present: token !== null });
620
782
  _storage.set(ACCESS_KEY, token);
621
783
  notifySessionChanged();
622
784
  },
@@ -624,14 +786,16 @@ var auth = {
624
786
  return _storage.get(REFRESH_KEY);
625
787
  },
626
788
  setRefreshToken(token) {
789
+ trace("store.setRefreshToken", { present: token !== null });
627
790
  _storage.set(REFRESH_KEY, token);
628
791
  notifySessionChanged();
629
792
  },
630
793
  clearTokens() {
794
+ trace("store.clearTokens");
631
795
  for (const mode of ["localStorage", "sessionStorage", "cookie"]) {
632
- const backend = backendFor(mode);
633
- backend.set(ACCESS_KEY, null);
634
- backend.set(REFRESH_KEY, null);
796
+ const backend2 = backendFor(mode);
797
+ backend2.set(ACCESS_KEY, null);
798
+ backend2.set(REFRESH_KEY, null);
635
799
  }
636
800
  storage.clearAll();
637
801
  notifySessionChanged();
@@ -650,6 +814,7 @@ var auth = {
650
814
  * rotation); `refresh: null` explicitly drops it.
651
815
  */
652
816
  setSession(tokens) {
817
+ trace("store.setSession", { refresh: tokens.refresh === void 0 ? "kept" : tokens.refresh === null ? "dropped" : "set" });
653
818
  _storage.set(ACCESS_KEY, tokens.access);
654
819
  if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
655
820
  notifySessionChanged();
@@ -788,7 +953,11 @@ async function tryRefresh() {
788
953
  const refresh = auth.getRefreshToken();
789
954
  if (!refresh) return null;
790
955
  const result = await _refreshHandler(refresh);
791
- if (!result?.access) return null;
956
+ if (!result?.access) {
957
+ trace("refresh", { ok: false, why: "no-access" });
958
+ return null;
959
+ }
960
+ trace("refresh", { ok: true, rotated: Boolean(result.refresh) });
792
961
  auth.setToken(result.access);
793
962
  if (result.refresh) auth.setRefreshToken(result.refresh);
794
963
  return result.access;
@@ -801,6 +970,7 @@ async function tryRefresh() {
801
970
  }
802
971
  return await runRefresh();
803
972
  } catch {
973
+ trace("refresh", { ok: false, why: "threw" });
804
974
  return null;
805
975
  } finally {
806
976
  _refreshInflight = null;
@@ -809,7 +979,8 @@ async function tryRefresh() {
809
979
  return _refreshInflight;
810
980
  }
811
981
  __name(tryRefresh, "tryRefresh");
812
- function expireSession(response) {
982
+ function expireSession(response, why) {
983
+ trace("store.expire", { why, status: response.status, path: pathOf(response.url), handlers: _sessionExpiredHandlers.size });
813
984
  auth.clearTokens();
814
985
  for (const cb of Array.from(_sessionExpiredHandlers)) {
815
986
  try {
@@ -952,15 +1123,16 @@ function installAuthOnClient(client2) {
952
1123
  client2.interceptors.response.use(async (response, request) => {
953
1124
  if (response.status !== 401) return response;
954
1125
  if (/\/accounts\/(otp|oauth|login|register|token)\b/.test(request.url)) {
1126
+ trace("401.passthrough", { path: pathOf(request.url) });
955
1127
  return response;
956
1128
  }
957
1129
  if (request.headers.get(RETRY_MARKER)) {
958
- expireSession(response);
1130
+ expireSession(response, "retry-marker");
959
1131
  return response;
960
1132
  }
961
1133
  const newToken = await tryRefresh();
962
1134
  if (!newToken) {
963
- expireSession(response);
1135
+ expireSession(response, "refresh-failed");
964
1136
  return response;
965
1137
  }
966
1138
  const retry = request.clone();
@@ -972,7 +1144,7 @@ function installAuthOnClient(client2) {
972
1144
  }
973
1145
  try {
974
1146
  const retried = await fetch(retry);
975
- if (retried.status === 401) expireSession(retried);
1147
+ if (retried.status === 401) expireSession(retried, "retry-401");
976
1148
  return retried;
977
1149
  } catch {
978
1150
  return response;
@@ -1196,6 +1368,16 @@ __name(useTimezone, "useTimezone");
1196
1368
  // src/auth/hooks/useCfgRouter.ts
1197
1369
  import { useRouter as useNextRouter } from "next/navigation";
1198
1370
  import { useCallback as useCallback2, useMemo } from "react";
1371
+ var hardNavigating = false;
1372
+ if (typeof window !== "undefined") {
1373
+ window.addEventListener("pageshow", (e) => {
1374
+ if (e.persisted) hardNavigating = false;
1375
+ });
1376
+ }
1377
+ function fromPath() {
1378
+ return typeof window === "undefined" ? "" : window.location.pathname;
1379
+ }
1380
+ __name(fromPath, "fromPath");
1199
1381
  function getBasePath() {
1200
1382
  if (typeof process === "undefined") {
1201
1383
  return "";
@@ -1216,6 +1398,7 @@ function useCfgRouter() {
1216
1398
  const router = useNextRouter();
1217
1399
  const basePath = useMemo(() => getBasePath(), []);
1218
1400
  const push = useCallback2((href, options) => {
1401
+ authTrace("nav.push", { from: fromPath(), to: href });
1219
1402
  if (basePath) {
1220
1403
  window.location.href = withBasePath(href, basePath);
1221
1404
  } else {
@@ -1223,6 +1406,7 @@ function useCfgRouter() {
1223
1406
  }
1224
1407
  }, [router, basePath]);
1225
1408
  const replace = useCallback2((href, options) => {
1409
+ authTrace("nav.replace", { from: fromPath(), to: href });
1226
1410
  if (basePath) {
1227
1411
  window.location.replace(withBasePath(href, basePath));
1228
1412
  } else {
@@ -1230,9 +1414,21 @@ function useCfgRouter() {
1230
1414
  }
1231
1415
  }, [router, basePath]);
1232
1416
  const hardPush = useCallback2((href) => {
1417
+ if (hardNavigating) {
1418
+ authTrace("nav.hardPush.ignored", { from: fromPath(), to: href });
1419
+ return;
1420
+ }
1421
+ hardNavigating = true;
1422
+ authTrace("nav.hardPush", { from: fromPath(), to: href });
1233
1423
  window.location.href = withBasePath(href, basePath);
1234
1424
  }, [basePath]);
1235
1425
  const hardReplace = useCallback2((href) => {
1426
+ if (hardNavigating) {
1427
+ authTrace("nav.hardReplace.ignored", { from: fromPath(), to: href });
1428
+ return;
1429
+ }
1430
+ hardNavigating = true;
1431
+ authTrace("nav.hardReplace", { from: fromPath(), to: href });
1236
1432
  window.location.replace(withBasePath(href, basePath));
1237
1433
  }, [basePath]);
1238
1434
  const prefetch = useCallback2((href) => {
@@ -3198,7 +3394,7 @@ var OAuthErrorCodeEnum = /* @__PURE__ */ ((OAuthErrorCodeEnum2) => {
3198
3394
 
3199
3395
  // src/auth/utils/errors.ts
3200
3396
  var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
3201
- var TRANSPORT_FALLBACK = /^HTTP \d{3}:\s*/;
3397
+ var TRANSPORT_FALLBACK = /^HTTP \d{1,3}:\s*/;
3202
3398
  var AUTH_ERROR_KEYS = {
3203
3399
  invalidCredentials: "ui.errors.unauthorized",
3204
3400
  rateLimited: "api.errors.rateLimited",
@@ -3215,6 +3411,7 @@ var FALLBACK_COPY = {
3215
3411
  };
3216
3412
  var keyForStatus = /* @__PURE__ */ __name((status) => {
3217
3413
  if (status === void 0) return AUTH_ERROR_KEYS.generic;
3414
+ if (status === 0) return AUTH_ERROR_KEYS.networkError;
3218
3415
  if (status === 401 || status === 403) return AUTH_ERROR_KEYS.invalidCredentials;
3219
3416
  if (status === 404) return AUTH_ERROR_KEYS.serverUnavailable;
3220
3417
  if (status === 429) return AUTH_ERROR_KEYS.rateLimited;
@@ -3259,9 +3456,9 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
3259
3456
 
3260
3457
  // src/auth/utils/oauthErrors.ts
3261
3458
  var SERVER_CODES = new Set(Object.values(OAuthErrorCodeEnum));
3262
- var record = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3459
+ var record2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3263
3460
  var readOAuthFailure = /* @__PURE__ */ __name((err, fallback) => {
3264
- const body = record(record(err)?.response);
3461
+ const body = record2(record2(err)?.response);
3265
3462
  const code = body?.error;
3266
3463
  if (typeof code === "string" && SERVER_CODES.has(code)) {
3267
3464
  const description = body?.error_description;
@@ -3540,6 +3737,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3540
3737
  setLoadingProvider(provider);
3541
3738
  setError(null);
3542
3739
  setErrorCode(null);
3740
+ authTrace("oauth.start");
3543
3741
  auth.clearSession();
3544
3742
  try {
3545
3743
  Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
@@ -3587,6 +3785,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3587
3785
  if (!response.access || !response.refresh) {
3588
3786
  throw new Error("Invalid response from OAuth callback");
3589
3787
  }
3788
+ authTrace("oauth.session");
3590
3789
  auth.setSession({ access: response.access, refresh: response.refresh });
3591
3790
  Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
3592
3791
  category: "auth" /* AUTH */,
@@ -3767,6 +3966,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
3767
3966
  setError(null);
3768
3967
  }, []);
3769
3968
  const handleSuccess = useCallback8((response) => {
3969
+ authTrace("2fa.session");
3770
3970
  auth.setSession({ access: response.access_token, refresh: response.refresh_token });
3771
3971
  if (response.warning) {
3772
3972
  setWarning(response.warning);
@@ -4418,6 +4618,17 @@ function purgeLegacyTokenStorage() {
4418
4618
  }
4419
4619
  __name(purgeLegacyTokenStorage, "purgeLegacyTokenStorage");
4420
4620
 
4621
+ // src/auth/utils/authState.ts
4622
+ function resolveAuthStatus(s) {
4623
+ if (!s.clientReady) return "resolving";
4624
+ if (s.snapshotAuthenticated) {
4625
+ return s.profileSettled || s.timedOut ? "authenticated" : "resolving";
4626
+ }
4627
+ if (s.storeAuthenticated && !s.timedOut) return "resolving";
4628
+ return "anonymous";
4629
+ }
4630
+ __name(resolveAuthStatus, "resolveAuthStatus");
4631
+
4421
4632
  // src/auth/context/AccountsContext.tsx
4422
4633
  import {
4423
4634
  createContext,
@@ -4873,6 +5084,7 @@ function AccountsProvider({ children }) {
4873
5084
  return;
4874
5085
  }
4875
5086
  if (result.access && result.refresh) {
5087
+ authTrace("accounts.otp.session");
4876
5088
  auth.setSession({ access: result.access, refresh: result.refresh });
4877
5089
  try {
4878
5090
  await refreshProfile({ callerId, force: true });
@@ -4898,6 +5110,7 @@ function AccountsProvider({ children }) {
4898
5110
  authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
4899
5111
  });
4900
5112
  }
5113
+ authTrace("accounts.logout");
4901
5114
  auth.clearSession();
4902
5115
  setProfile(void 0);
4903
5116
  setProfileError(null);
@@ -4932,11 +5145,11 @@ function AccountsProvider({ children }) {
4932
5145
  }
4933
5146
  __name(AccountsProvider, "AccountsProvider");
4934
5147
  function useAccountsContext() {
4935
- const context = useContext(AccountsContext);
4936
- if (!context) {
5148
+ const context2 = useContext(AccountsContext);
5149
+ if (!context2) {
4937
5150
  throw new Error("useAccountsContext must be used within AccountsProvider");
4938
5151
  }
4939
- return context;
5152
+ return context2;
4940
5153
  }
4941
5154
  __name(useAccountsContext, "useAccountsContext");
4942
5155
 
@@ -4992,14 +5205,15 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4992
5205
  const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
4993
5206
  if (hasTokens && !auth.isAuthenticated()) {
4994
5207
  authLogger.warn("Dead session in storage \u2014 clearing");
5208
+ authTrace("bootstrap.purge", { why: "dead-tokens" });
4995
5209
  auth.clearSession();
4996
5210
  clearProfileCache();
4997
5211
  } else if (!hasTokens && !auth.isAuthenticated()) {
5212
+ authTrace("bootstrap.purge", { why: "no-session" });
4998
5213
  clearProfileCache();
4999
5214
  purgeLegacyTokenStorage();
5000
5215
  if (accountsRef.current.profile) accountsRef.current.logout();
5001
5216
  }
5002
- if (auth.isAuthenticated()) return;
5003
5217
  setProfileSettled(true);
5004
5218
  return;
5005
5219
  }
@@ -5008,9 +5222,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5008
5222
  return;
5009
5223
  }
5010
5224
  profileAttemptedRef.current = true;
5225
+ authTrace("bootstrap.profile.start");
5011
5226
  let cancelled = false;
5012
5227
  accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
5013
5228
  authLogger.warn("Profile load failed (session kept):", error);
5229
+ authTrace("bootstrap.profile.failed", {
5230
+ status: error instanceof APIError ? error.statusCode : 0
5231
+ });
5014
5232
  }).finally(() => {
5015
5233
  if (!cancelled) setProfileSettled(true);
5016
5234
  });
@@ -5019,11 +5237,47 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5019
5237
  };
5020
5238
  }, [isAuthenticated, hasProfile]);
5021
5239
  const isLoading = !profileSettled;
5022
- const expiredHandledRef = useRef7(false);
5240
+ const [clientReady, setClientReady] = useState12(false);
5241
+ const [timedOut, setTimedOut] = useState12(false);
5242
+ useEffect7(() => {
5243
+ setClientReady(true);
5244
+ const timer = setTimeout(() => setTimedOut(true), 8e3);
5245
+ return () => clearTimeout(timer);
5246
+ }, []);
5247
+ const status = resolveAuthStatus({
5248
+ clientReady,
5249
+ snapshotAuthenticated: isAuthenticated,
5250
+ storeAuthenticated: isBrowser2 ? auth.isAuthenticated() : false,
5251
+ profileSettled,
5252
+ timedOut
5253
+ });
5254
+ const lastStatusRef = useRef7("");
5255
+ useEffect7(() => {
5256
+ if (lastStatusRef.current === status) return;
5257
+ lastStatusRef.current = status;
5258
+ authTrace("status", {
5259
+ status,
5260
+ snapshot: isAuthenticated,
5261
+ store: auth.isAuthenticated(),
5262
+ profileSettled,
5263
+ timedOut,
5264
+ path: pathname ?? ""
5265
+ });
5266
+ }, [status, isAuthenticated, profileSettled, timedOut, pathname]);
5267
+ useEffect7(() => {
5268
+ globalThis.__cfgAuthNow = () => ({
5269
+ status,
5270
+ snapshot: isAuthenticated,
5271
+ store: auth.isAuthenticated(),
5272
+ hasToken: Boolean(auth.getToken()),
5273
+ hasRefresh: Boolean(auth.getRefreshToken()),
5274
+ profileSettled,
5275
+ timedOut
5276
+ });
5277
+ }, [status, isAuthenticated, profileSettled, timedOut]);
5023
5278
  useEffect7(() => {
5024
5279
  const unsubscribe = auth.onSessionExpired(() => {
5025
- if (expiredHandledRef.current) return;
5026
- expiredHandledRef.current = true;
5280
+ authTrace("expired.handler", { path: pathname ?? "" });
5027
5281
  authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
5028
5282
  clearProfileCache();
5029
5283
  Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
@@ -5032,12 +5286,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5032
5286
  router.hardReplace(routesRef.current.defaultAuthCallback);
5033
5287
  });
5034
5288
  return unsubscribe;
5035
- }, [router]);
5289
+ }, [router, pathname]);
5036
5290
  useEffect7(() => {
5037
5291
  if (!isAuthenticated || pendingManualNavRef.current) return;
5038
5292
  if (pathname === routes.auth && !queryParams.get("flow")) {
5039
5293
  const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
5040
5294
  if (saved) redirectManager.clearRedirect();
5295
+ authTrace("leave-auth", { to: saved || routes.defaultCallback, saved: Boolean(saved) });
5041
5296
  router.push(saved || routes.defaultCallback);
5042
5297
  }
5043
5298
  }, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
@@ -5063,6 +5318,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5063
5318
  }, [loadCurrentProfile, router]);
5064
5319
  const requestOTP = useCallback13(
5065
5320
  async (identifier, sourceUrl, consent) => {
5321
+ authTrace("otp.request");
5066
5322
  auth.clearSession();
5067
5323
  try {
5068
5324
  const body = {
@@ -5107,6 +5363,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5107
5363
  async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
5108
5364
  try {
5109
5365
  pendingManualNavRef.current = Boolean(skipRedirect);
5366
+ authTrace("otp.verify", { skipRedirect: Boolean(skipRedirect) });
5110
5367
  const result = await accountsRef.current.verifyOTP({
5111
5368
  identifier,
5112
5369
  otp: otpCode,
@@ -5168,6 +5425,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5168
5425
  const { redirectUrl, skipRedirect } = options || {};
5169
5426
  try {
5170
5427
  pendingManualNavRef.current = Boolean(skipRedirect);
5428
+ authTrace("password.login", { skipRedirect: Boolean(skipRedirect) });
5171
5429
  const result = await accountsRef.current.loginWithPassword({
5172
5430
  email,
5173
5431
  password
@@ -5242,6 +5500,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5242
5500
  return { success: false, message: "Error refreshing token" };
5243
5501
  }, []);
5244
5502
  const logout = useCallback13(() => {
5503
+ authTrace("logout");
5245
5504
  Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
5246
5505
  category: "auth" /* AUTH */
5247
5506
  });
@@ -5268,6 +5527,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5268
5527
  () => ({
5269
5528
  user,
5270
5529
  isLoading,
5530
+ status,
5271
5531
  // Reactive: flips via the store snapshot (token writes, refreshes,
5272
5532
  // expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
5273
5533
  isAuthenticated,
@@ -5295,6 +5555,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5295
5555
  [
5296
5556
  user,
5297
5557
  isLoading,
5558
+ status,
5298
5559
  isAuthenticated,
5299
5560
  isAdminUser,
5300
5561
  routes,
@@ -5324,6 +5585,7 @@ var AuthProvider = memo(AuthProviderRaw);
5324
5585
  var defaultAuthState = {
5325
5586
  user: null,
5326
5587
  isLoading: false,
5588
+ status: "anonymous",
5327
5589
  isAuthenticated: false,
5328
5590
  isAdminUser: false,
5329
5591
  routes: resolveAuthRoutes(),
@@ -5376,14 +5638,14 @@ var defaultAuthState = {
5376
5638
  }, "uploadAvatar")
5377
5639
  };
5378
5640
  var useAuth = /* @__PURE__ */ __name(() => {
5379
- const context = useContext2(AuthContext);
5380
- if (context === void 0) {
5641
+ const context2 = useContext2(AuthContext);
5642
+ if (context2 === void 0) {
5381
5643
  if (isBrowser2 && isDev) {
5382
5644
  authLogger.debug("useAuth called outside AuthProvider, returning default state");
5383
5645
  }
5384
5646
  return defaultAuthState;
5385
5647
  }
5386
- return context;
5648
+ return context2;
5387
5649
  }, "useAuth");
5388
5650
  var AuthContext_default = AuthContext;
5389
5651
 
@@ -5426,17 +5688,21 @@ export {
5426
5688
  AuthProvider,
5427
5689
  OAUTH_PROVIDER_IDS,
5428
5690
  authLogger,
5691
+ authTrace,
5429
5692
  beginTwoFactorChallenge,
5693
+ clearAuthTrace,
5430
5694
  clearOAuthAttempt,
5431
5695
  clearProfileCache,
5432
5696
  consumeSavedRedirect,
5433
5697
  decodeBase64,
5698
+ dumpAuthTrace,
5434
5699
  encodeBase64,
5435
5700
  endTwoFactorChallenge,
5436
5701
  formatAuthError,
5437
5702
  getCacheMetadata,
5438
5703
  getCachedProfile,
5439
5704
  hasValidCache,
5705
+ installAuthTrace,
5440
5706
  isAllowedAuthPath,
5441
5707
  isForbidden,
5442
5708
  isOAuthProviderId,
@@ -5444,8 +5710,10 @@ export {
5444
5710
  logger,
5445
5711
  normalizePath,
5446
5712
  peekSavedRedirect,
5713
+ readAuthTrace,
5447
5714
  readOAuthFailure,
5448
5715
  resolveAuthError,
5716
+ resolveAuthStatus,
5449
5717
  resolveGuardIsAuthenticated,
5450
5718
  resolveGuardIsLoading,
5451
5719
  setAnalyticsSink,