@djangocfg/api 2.1.619 → 2.1.622

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.cjs CHANGED
@@ -216,17 +216,21 @@ __export(auth_exports, {
216
216
  AuthProvider: () => AuthProvider,
217
217
  OAUTH_PROVIDER_IDS: () => OAUTH_PROVIDER_IDS,
218
218
  authLogger: () => authLogger,
219
+ authTrace: () => authTrace,
219
220
  beginTwoFactorChallenge: () => beginTwoFactorChallenge,
221
+ clearAuthTrace: () => clearAuthTrace,
220
222
  clearOAuthAttempt: () => clearOAuthAttempt,
221
223
  clearProfileCache: () => clearProfileCache,
222
224
  consumeSavedRedirect: () => consumeSavedRedirect,
223
225
  decodeBase64: () => decodeBase64,
226
+ dumpAuthTrace: () => dumpAuthTrace,
224
227
  encodeBase64: () => encodeBase64,
225
228
  endTwoFactorChallenge: () => endTwoFactorChallenge,
226
229
  formatAuthError: () => formatAuthError,
227
230
  getCacheMetadata: () => getCacheMetadata,
228
231
  getCachedProfile: () => getCachedProfile,
229
232
  hasValidCache: () => hasValidCache,
233
+ installAuthTrace: () => installAuthTrace,
230
234
  isAllowedAuthPath: () => isAllowedAuthPath,
231
235
  isForbidden: () => isForbidden,
232
236
  isOAuthProviderId: () => isOAuthProviderId,
@@ -234,8 +238,11 @@ __export(auth_exports, {
234
238
  logger: () => logger,
235
239
  normalizePath: () => normalizePath,
236
240
  peekSavedRedirect: () => peekSavedRedirect,
241
+ readAuthTrace: () => readAuthTrace,
237
242
  readOAuthFailure: () => readOAuthFailure,
243
+ resetAuthTraceForTests: () => resetAuthTraceForTests,
238
244
  resolveAuthError: () => resolveAuthError,
245
+ resolveAuthStatus: () => resolveAuthStatus,
239
246
  resolveGuardIsAuthenticated: () => resolveGuardIsAuthenticated,
240
247
  resolveGuardIsLoading: () => resolveGuardIsLoading,
241
248
  setAnalyticsSink: () => setAnalyticsSink,
@@ -281,9 +288,201 @@ var AUTH_CONSTANTS = {
281
288
  RESEND_COOLDOWN_SECONDS: 30
282
289
  };
283
290
 
291
+ // src/auth/trace.ts
292
+ var KEY = "cfg.auth.trace";
293
+ var MAX = 200;
294
+ var MAX_STRING = 120;
295
+ var SENSITIVE = /token|otp|code|email|password|secret|cookie|authorization|key|identifier/i;
296
+ var TAB = (() => {
297
+ const g = globalThis;
298
+ return g.__cfgAuthTab ??= Math.random().toString(36).slice(2, 6);
299
+ })();
300
+ var hasWindow = typeof window !== "undefined";
301
+ var memory = [];
302
+ var loaded = false;
303
+ function backend() {
304
+ if (!hasWindow) return null;
305
+ for (const name of ["localStorage", "sessionStorage"]) {
306
+ try {
307
+ const s = window[name];
308
+ s.setItem("__cfg_probe", "1");
309
+ s.removeItem("__cfg_probe");
310
+ return s;
311
+ } catch {
312
+ }
313
+ }
314
+ return null;
315
+ }
316
+ __name(backend, "backend");
317
+ function load() {
318
+ if (loaded) return memory;
319
+ loaded = true;
320
+ try {
321
+ const raw = backend()?.getItem(KEY);
322
+ const parsed = raw ? JSON.parse(raw) : [];
323
+ if (Array.isArray(parsed)) memory = parsed.filter((e) => typeof e === "object" && e !== null);
324
+ } catch {
325
+ memory = [];
326
+ }
327
+ return memory;
328
+ }
329
+ __name(load, "load");
330
+ function persist() {
331
+ try {
332
+ backend()?.setItem(KEY, JSON.stringify(memory));
333
+ } catch {
334
+ }
335
+ }
336
+ __name(persist, "persist");
337
+ function clean(fields) {
338
+ if (!fields) return void 0;
339
+ const out = {};
340
+ for (const [k2, v] of Object.entries(fields)) {
341
+ if (SENSITIVE.test(k2)) continue;
342
+ if (typeof v === "string") out[k2] = v.length > MAX_STRING ? `${v.slice(0, MAX_STRING)}\u2026` : v;
343
+ else if (typeof v === "number" || typeof v === "boolean" || v === null) out[k2] = v;
344
+ }
345
+ return Object.keys(out).length ? out : void 0;
346
+ }
347
+ __name(clean, "clean");
348
+ function refresh() {
349
+ try {
350
+ const raw = backend()?.getItem(KEY);
351
+ const parsed = raw ? JSON.parse(raw) : null;
352
+ if (Array.isArray(parsed)) memory = parsed.filter((e) => typeof e === "object" && e !== null);
353
+ } catch {
354
+ }
355
+ }
356
+ __name(refresh, "refresh");
357
+ function record(at, ev, fields) {
358
+ load();
359
+ refresh();
360
+ const entries = memory;
361
+ const p = hasWindow && typeof performance !== "undefined" ? Math.round(performance.now()) : 0;
362
+ const f2 = clean(fields);
363
+ const last = entries[entries.length - 1];
364
+ if (last && last.tab === TAB && last.ev === ev && JSON.stringify(last.f) === JSON.stringify(f2)) {
365
+ last.n = (last.n ?? 1) + 1;
366
+ last.t = at;
367
+ last.p = p;
368
+ persist();
369
+ return;
370
+ }
371
+ entries.push({ t: at, p, tab: TAB, ev, f: f2 });
372
+ if (entries.length > MAX) entries.splice(0, entries.length - MAX);
373
+ persist();
374
+ }
375
+ __name(record, "record");
376
+ function authTrace(ev, fields) {
377
+ if (!hasWindow) return;
378
+ try {
379
+ record(Date.now(), ev, fields);
380
+ } catch {
381
+ }
382
+ }
383
+ __name(authTrace, "authTrace");
384
+ function resetAuthTraceForTests() {
385
+ memory = [];
386
+ loaded = true;
387
+ }
388
+ __name(resetAuthTraceForTests, "resetAuthTraceForTests");
389
+ function readAuthTrace() {
390
+ return load().slice();
391
+ }
392
+ __name(readAuthTrace, "readAuthTrace");
393
+ function clearAuthTrace() {
394
+ memory = [];
395
+ loaded = true;
396
+ try {
397
+ backend()?.removeItem(KEY);
398
+ } catch {
399
+ }
400
+ }
401
+ __name(clearAuthTrace, "clearAuthTrace");
402
+ function context() {
403
+ if (!hasWindow) return [];
404
+ const copies = globalThis.__cfgAuthCopies ?? 1;
405
+ const lines = [`url: ${window.location.href.split("?")[0]}`, `copies: ${String(copies)}`, `online: ${navigator.onLine}`, `ua: ${navigator.userAgent}`];
406
+ try {
407
+ const auth2 = globalThis.__cfgAuthNow;
408
+ const now = auth2?.();
409
+ if (now) lines.push(`now: ${Object.entries(now).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}`);
410
+ } catch {
411
+ }
412
+ return lines;
413
+ }
414
+ __name(context, "context");
415
+ function dumpAuthTrace() {
416
+ const entries = load();
417
+ const first = entries[0]?.t ?? Date.now();
418
+ const rows = entries.map((e) => {
419
+ const fields = e.f ? ` ${Object.entries(e.f).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}` : "";
420
+ const at = new Date(e.t).toISOString().slice(11, 23);
421
+ return `${at} +${String(e.t - first).padStart(6)}ms [${e.tab} p${e.p}] ${e.ev}${fields}${e.n && e.n > 1 ? ` x${e.n}` : ""}`;
422
+ });
423
+ return [`# cfg auth trace, ${entries.length} events`, ...context(), "", ...rows].join("\n");
424
+ }
425
+ __name(dumpAuthTrace, "dumpAuthTrace");
426
+ function installAuthTrace() {
427
+ if (!hasWindow) return;
428
+ const g = globalThis;
429
+ g.__cfgAuthTrace = authTrace;
430
+ g.__cfgAuth = {
431
+ trace: readAuthTrace,
432
+ dump: /* @__PURE__ */ __name(() => {
433
+ const text = dumpAuthTrace();
434
+ console.log(text);
435
+ return text;
436
+ }, "dump"),
437
+ clear: clearAuthTrace,
438
+ /** Put the dump on the clipboard (works when the console output is blocked or too long to select). */
439
+ copy: /* @__PURE__ */ __name(() => {
440
+ const text = dumpAuthTrace();
441
+ void navigator.clipboard?.writeText(text);
442
+ return `${text.split("\n").length} lines copied`;
443
+ }, "copy"),
444
+ /** Download the raw trace as a JSON file. */
445
+ download: /* @__PURE__ */ __name(() => {
446
+ const blob = new Blob([JSON.stringify(readAuthTrace(), null, 1)], { type: "application/json" });
447
+ const a = document.createElement("a");
448
+ a.href = URL.createObjectURL(blob);
449
+ a.download = `auth-trace-${Date.now()}.json`;
450
+ a.click();
451
+ }, "download")
452
+ };
453
+ const queued = g.__cfgAuthTraceQ;
454
+ if (Array.isArray(queued)) {
455
+ g.__cfgAuthTraceQ = [];
456
+ for (const item of queued) {
457
+ if (Array.isArray(item) && typeof item[1] === "string") {
458
+ try {
459
+ record(Number(item[0]) || Date.now(), item[1], item[2]);
460
+ } catch {
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+ __name(installAuthTrace, "installAuthTrace");
467
+ function registerCopy() {
468
+ if (!hasWindow) return;
469
+ const g = globalThis;
470
+ const n = (g.__cfgAuthCopies ?? 0) + 1;
471
+ g.__cfgAuthCopies = n;
472
+ if (n > 1) {
473
+ authTrace("dup.copy", { copies: n });
474
+ if (n === 2) {
475
+ console.warn("[@djangocfg/api] more than one copy of the auth package is loaded; see __cfgAuth.dump() (dup.copy). Align the peer set so pnpm installs one instance.");
476
+ }
477
+ }
478
+ }
479
+ __name(registerCopy, "registerCopy");
480
+ registerCopy();
481
+ installAuthTrace();
482
+
284
483
  // src/auth/context/AuthContext.tsx
285
484
  var import_navigation4 = require("next/navigation");
286
- var import_react20 = require("react");
485
+ var import_react21 = require("react");
287
486
 
288
487
  // src/_api/generated/helpers/errors.ts
289
488
  var APIError = class extends Error {
@@ -388,10 +587,10 @@ function normalizeSegment(raw) {
388
587
  return raw.trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
389
588
  }
390
589
  __name(normalizeSegment, "normalizeSegment");
391
- var hasWindow = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
590
+ var hasWindow2 = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
392
591
  var domainByHost = /* @__PURE__ */ new Map();
393
592
  function detectCookieDomain() {
394
- if (!hasWindow() || typeof document === "undefined") return null;
593
+ if (!hasWindow2() || typeof document === "undefined") return null;
395
594
  const host = window.location.hostname.toLowerCase();
396
595
  if (!host || !host.includes(".") || host.includes(":") || /^[0-9.]+$/.test(host)) return null;
397
596
  const cached = domainByHost.get(host);
@@ -415,7 +614,7 @@ function detectCookieDomain() {
415
614
  }
416
615
  __name(detectCookieDomain, "detectCookieDomain");
417
616
  function nativeStorage(store) {
418
- if (!hasWindow()) return null;
617
+ if (!hasWindow2()) return null;
419
618
  try {
420
619
  return store === "session" ? window.sessionStorage : window.localStorage;
421
620
  } catch {
@@ -424,7 +623,7 @@ function nativeStorage(store) {
424
623
  }
425
624
  __name(nativeStorage, "nativeStorage");
426
625
  function cookieNames() {
427
- if (!hasWindow()) return [];
626
+ if (!hasWindow2()) return [];
428
627
  try {
429
628
  return document.cookie.split("; ").map((part) => decodeURIComponent(part.split("=")[0] ?? "")).filter(Boolean);
430
629
  } catch {
@@ -449,7 +648,7 @@ function createStorage(owner, options = {}) {
449
648
  const families = Object.values(options.families ?? {});
450
649
  const specByKey = /* @__PURE__ */ new Map();
451
650
  for (const spec of specs) specByKey.set(spec.key, [...specByKey.get(spec.key) ?? [], spec]);
452
- const memory = {
651
+ const memory2 = {
453
652
  local: /* @__PURE__ */ new Map(),
454
653
  session: /* @__PURE__ */ new Map(),
455
654
  cookie: /* @__PURE__ */ new Map()
@@ -463,7 +662,7 @@ function createStorage(owner, options = {}) {
463
662
  if (!key.startsWith(prefix)) throw new TypeError(`storage key "${key}" is not owned by "${owner}"`);
464
663
  }, "assertOwned");
465
664
  function rawGet(store, key) {
466
- if (!hasWindow()) return null;
665
+ if (!hasWindow2()) return null;
467
666
  if (store === "cookie") {
468
667
  try {
469
668
  const escaped = encodeURIComponent(key).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -471,18 +670,18 @@ function createStorage(owner, options = {}) {
471
670
  if (match?.[1] !== void 0) return decodeURIComponent(match[1]);
472
671
  } catch {
473
672
  }
474
- return memory.cookie.get(key) ?? null;
673
+ return memory2.cookie.get(key) ?? null;
475
674
  }
476
675
  try {
477
676
  const value = nativeStorage(store)?.getItem(key) ?? null;
478
677
  if (value !== null) return value;
479
678
  } catch {
480
679
  }
481
- return memory[store].get(key) ?? null;
680
+ return memory2[store].get(key) ?? null;
482
681
  }
483
682
  __name(rawGet, "rawGet");
484
683
  function rawSet(store, key, value, maxAgeSeconds) {
485
- if (!hasWindow()) return false;
684
+ if (!hasWindow2()) return false;
486
685
  try {
487
686
  if (store === "cookie") {
488
687
  const secure = window.location.protocol === "https:" ? "; Secure" : "";
@@ -490,24 +689,24 @@ function createStorage(owner, options = {}) {
490
689
  const domainPart = domain ? `; Domain=${domain}` : "";
491
690
  document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; Path=/; Max-Age=${Math.max(1, Math.floor(maxAgeSeconds ?? cookieMaxAge))}; SameSite=Lax${domainPart}${secure}`;
492
691
  if (domain) expireCookie(key, null);
493
- if (rawGet("cookie", key) === value && !memory.cookie.has(key)) return true;
494
- memory.cookie.set(key, value);
692
+ if (rawGet("cookie", key) === value && !memory2.cookie.has(key)) return true;
693
+ memory2.cookie.set(key, value);
495
694
  return false;
496
695
  }
497
696
  const native = nativeStorage(store);
498
697
  if (!native) throw new Error("storage unavailable");
499
698
  native.setItem(key, value);
500
- memory[store].delete(key);
699
+ memory2[store].delete(key);
501
700
  return true;
502
701
  } catch {
503
- memory[store].set(key, value);
702
+ memory2[store].set(key, value);
504
703
  return false;
505
704
  }
506
705
  }
507
706
  __name(rawSet, "rawSet");
508
707
  function rawRemove(store, key) {
509
- memory[store].delete(key);
510
- if (!hasWindow()) return;
708
+ memory2[store].delete(key);
709
+ if (!hasWindow2()) return;
511
710
  try {
512
711
  if (store === "cookie") {
513
712
  expireCookie(key, null);
@@ -536,7 +735,7 @@ function createStorage(owner, options = {}) {
536
735
  }
537
736
  } catch {
538
737
  }
539
- for (const name of memory[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
738
+ for (const name of memory2[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
540
739
  return out;
541
740
  }
542
741
  __name(listKeys, "listKeys");
@@ -608,7 +807,7 @@ function createStorage(owner, options = {}) {
608
807
  cookieDomain: resolveCookieDomain,
609
808
  onChange(key, listener, store = "local") {
610
809
  assertValid(key);
611
- if (!hasWindow()) return () => {
810
+ if (!hasWindow2()) return () => {
612
811
  };
613
812
  const handler = /* @__PURE__ */ __name((event) => {
614
813
  if (event.storageArea !== nativeStorage(store)) return;
@@ -632,6 +831,29 @@ var REFRESH_KEY = "cfg.auth.refresh_token";
632
831
  var API_KEY_KEY = "cfg.auth.api_key";
633
832
  var storage = true ? cfgStorage : createStorage2("cfg");
634
833
  var isBrowser = typeof window !== "undefined";
834
+ function trace(ev, fields) {
835
+ try {
836
+ const g = globalThis;
837
+ const sink2 = g.__cfgAuthTrace;
838
+ if (sink2) {
839
+ sink2(ev, fields);
840
+ return;
841
+ }
842
+ const queue = g.__cfgAuthTraceQ ?? [];
843
+ g.__cfgAuthTraceQ = queue;
844
+ if (queue.length < 100) queue.push([Date.now(), ev, fields]);
845
+ } catch {
846
+ }
847
+ }
848
+ __name(trace, "trace");
849
+ function pathOf(url) {
850
+ try {
851
+ return new URL(url, "http://x").pathname;
852
+ } catch {
853
+ return "";
854
+ }
855
+ }
856
+ __name(pathOf, "pathOf");
635
857
  function secondsUntilExp(token) {
636
858
  const exp = jwtExpMs(token);
637
859
  return exp === null ? void 0 : Math.max(1, Math.floor((exp - Date.now()) / 1e3));
@@ -662,10 +884,10 @@ function adoptLegacySession() {
662
884
  if (_storage.get(ACCESS_KEY) !== null || _storage.get(REFRESH_KEY) !== null) return;
663
885
  const legacy = backendFor("localStorage");
664
886
  const access = legacy.get(ACCESS_KEY);
665
- const refresh = legacy.get(REFRESH_KEY);
666
- if (access === null && refresh === null) return;
887
+ const refresh2 = legacy.get(REFRESH_KEY);
888
+ if (access === null && refresh2 === null) return;
667
889
  if (access !== null) _storage.set(ACCESS_KEY, access);
668
- if (refresh !== null) _storage.set(REFRESH_KEY, refresh);
890
+ if (refresh2 !== null) _storage.set(REFRESH_KEY, refresh2);
669
891
  legacy.set(ACCESS_KEY, null);
670
892
  legacy.set(REFRESH_KEY, null);
671
893
  } catch {
@@ -750,12 +972,12 @@ function jwtExpMs(token) {
750
972
  __name(jwtExpMs, "jwtExpMs");
751
973
  function computeSnapshot() {
752
974
  const access = _storage.get(ACCESS_KEY);
753
- const refresh = _storage.get(REFRESH_KEY);
975
+ const refresh2 = _storage.get(REFRESH_KEY);
754
976
  const now = Date.now();
755
977
  const accessExp = access ? jwtExpMs(access) : null;
756
978
  const accessAlive = access !== null && (accessExp === null || accessExp > now);
757
- const refreshExp = refresh ? jwtExpMs(refresh) : null;
758
- const refreshAlive = refresh !== null && (refreshExp === null || refreshExp > now);
979
+ const refreshExp = refresh2 ? jwtExpMs(refresh2) : null;
980
+ const refreshAlive = refresh2 !== null && (refreshExp === null || refreshExp > now);
759
981
  return {
760
982
  status: accessAlive || refreshAlive ? "authenticated" : "anonymous",
761
983
  accessExpiresAt: accessExp
@@ -778,9 +1000,9 @@ function scheduleExpiryFlip() {
778
1000
  const now = Date.now();
779
1001
  const exps = [];
780
1002
  const access = _storage.get(ACCESS_KEY);
781
- const refresh = _storage.get(REFRESH_KEY);
1003
+ const refresh2 = _storage.get(REFRESH_KEY);
782
1004
  const accessExp = access ? jwtExpMs(access) : null;
783
- const refreshExp = refresh ? jwtExpMs(refresh) : null;
1005
+ const refreshExp = refresh2 ? jwtExpMs(refresh2) : null;
784
1006
  if (accessExp !== null && accessExp > now) exps.push(accessExp);
785
1007
  if (refreshExp !== null && refreshExp > now) exps.push(refreshExp);
786
1008
  if (!exps.length) return;
@@ -791,7 +1013,10 @@ __name(scheduleExpiryFlip, "scheduleExpiryFlip");
791
1013
  function notifySessionChanged() {
792
1014
  const next = computeSnapshot();
793
1015
  const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
794
- if (changed) _snapshot = next;
1016
+ if (changed) {
1017
+ _snapshot = next;
1018
+ trace("store.snapshot", { status: next.status });
1019
+ }
795
1020
  scheduleExpiryFlip();
796
1021
  if (!changed) return;
797
1022
  for (const listener of Array.from(_sessionListeners)) {
@@ -874,6 +1099,7 @@ var auth = {
874
1099
  return _storage.get(ACCESS_KEY);
875
1100
  },
876
1101
  setToken(token) {
1102
+ trace("store.setToken", { present: token !== null });
877
1103
  _storage.set(ACCESS_KEY, token);
878
1104
  notifySessionChanged();
879
1105
  },
@@ -881,14 +1107,16 @@ var auth = {
881
1107
  return _storage.get(REFRESH_KEY);
882
1108
  },
883
1109
  setRefreshToken(token) {
1110
+ trace("store.setRefreshToken", { present: token !== null });
884
1111
  _storage.set(REFRESH_KEY, token);
885
1112
  notifySessionChanged();
886
1113
  },
887
1114
  clearTokens() {
1115
+ trace("store.clearTokens");
888
1116
  for (const mode of ["localStorage", "sessionStorage", "cookie"]) {
889
- const backend = backendFor(mode);
890
- backend.set(ACCESS_KEY, null);
891
- backend.set(REFRESH_KEY, null);
1117
+ const backend2 = backendFor(mode);
1118
+ backend2.set(ACCESS_KEY, null);
1119
+ backend2.set(REFRESH_KEY, null);
892
1120
  }
893
1121
  storage.clearAll();
894
1122
  notifySessionChanged();
@@ -907,6 +1135,7 @@ var auth = {
907
1135
  * rotation); `refresh: null` explicitly drops it.
908
1136
  */
909
1137
  setSession(tokens) {
1138
+ trace("store.setSession", { refresh: tokens.refresh === void 0 ? "kept" : tokens.refresh === null ? "dropped" : "set" });
910
1139
  _storage.set(ACCESS_KEY, tokens.access);
911
1140
  if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
912
1141
  notifySessionChanged();
@@ -1042,10 +1271,14 @@ async function tryRefresh() {
1042
1271
  if (_refreshInflight) return _refreshInflight;
1043
1272
  if (!_refreshHandler) return null;
1044
1273
  const runRefresh = /* @__PURE__ */ __name(async () => {
1045
- const refresh = auth.getRefreshToken();
1046
- if (!refresh) return null;
1047
- const result = await _refreshHandler(refresh);
1048
- if (!result?.access) return null;
1274
+ const refresh2 = auth.getRefreshToken();
1275
+ if (!refresh2) return null;
1276
+ const result = await _refreshHandler(refresh2);
1277
+ if (!result?.access) {
1278
+ trace("refresh", { ok: false, why: "no-access" });
1279
+ return null;
1280
+ }
1281
+ trace("refresh", { ok: true, rotated: Boolean(result.refresh) });
1049
1282
  auth.setToken(result.access);
1050
1283
  if (result.refresh) auth.setRefreshToken(result.refresh);
1051
1284
  return result.access;
@@ -1058,6 +1291,7 @@ async function tryRefresh() {
1058
1291
  }
1059
1292
  return await runRefresh();
1060
1293
  } catch {
1294
+ trace("refresh", { ok: false, why: "threw" });
1061
1295
  return null;
1062
1296
  } finally {
1063
1297
  _refreshInflight = null;
@@ -1066,7 +1300,8 @@ async function tryRefresh() {
1066
1300
  return _refreshInflight;
1067
1301
  }
1068
1302
  __name(tryRefresh, "tryRefresh");
1069
- function expireSession(response) {
1303
+ function expireSession(response, why) {
1304
+ trace("store.expire", { why, status: response.status, path: pathOf(response.url), handlers: _sessionExpiredHandlers.size });
1070
1305
  auth.clearTokens();
1071
1306
  for (const cb of Array.from(_sessionExpiredHandlers)) {
1072
1307
  try {
@@ -1209,15 +1444,16 @@ function installAuthOnClient(client2) {
1209
1444
  client2.interceptors.response.use(async (response, request) => {
1210
1445
  if (response.status !== 401) return response;
1211
1446
  if (/\/accounts\/(otp|oauth|login|register|token)\b/.test(request.url)) {
1447
+ trace("401.passthrough", { path: pathOf(request.url) });
1212
1448
  return response;
1213
1449
  }
1214
1450
  if (request.headers.get(RETRY_MARKER)) {
1215
- expireSession(response);
1451
+ expireSession(response, "retry-marker");
1216
1452
  return response;
1217
1453
  }
1218
1454
  const newToken = await tryRefresh();
1219
1455
  if (!newToken) {
1220
- expireSession(response);
1456
+ expireSession(response, "refresh-failed");
1221
1457
  return response;
1222
1458
  }
1223
1459
  const retry = request.clone();
@@ -1229,7 +1465,7 @@ function installAuthOnClient(client2) {
1229
1465
  }
1230
1466
  try {
1231
1467
  const retried = await fetch(retry);
1232
- if (retried.status === 401) expireSession(retried);
1468
+ if (retried.status === 401) expireSession(retried, "retry-401");
1233
1469
  return retried;
1234
1470
  } catch {
1235
1471
  return response;
@@ -1238,7 +1474,7 @@ function installAuthOnClient(client2) {
1238
1474
  }
1239
1475
  __name(installAuthOnClient, "installAuthOnClient");
1240
1476
  var REFRESH_ENDPOINT = "/cfg/accounts/token/refresh/";
1241
- auth.setRefreshHandler(async (refresh) => {
1477
+ auth.setRefreshHandler(async (refresh2) => {
1242
1478
  try {
1243
1479
  const url = `${auth.getBaseUrl()}${REFRESH_ENDPOINT}`;
1244
1480
  const headers = { "Content-Type": "application/json" };
@@ -1250,11 +1486,11 @@ auth.setRefreshHandler(async (refresh) => {
1250
1486
  method: "POST",
1251
1487
  headers,
1252
1488
  credentials: auth.getWithCredentials() ? "include" : "same-origin",
1253
- body: JSON.stringify({ refresh })
1489
+ body: JSON.stringify({ refresh: refresh2 })
1254
1490
  });
1255
1491
  if (!res.ok) return null;
1256
1492
  const data = await res.json();
1257
- return data?.access ? { access: data.access, refresh: data.refresh ?? refresh } : null;
1493
+ return data?.access ? { access: data.access, refresh: data.refresh ?? refresh2 } : null;
1258
1494
  } catch {
1259
1495
  return null;
1260
1496
  }
@@ -1453,6 +1689,16 @@ __name(useTimezone, "useTimezone");
1453
1689
  // src/auth/hooks/useCfgRouter.ts
1454
1690
  var import_navigation = require("next/navigation");
1455
1691
  var import_react3 = require("react");
1692
+ var hardNavigating = false;
1693
+ if (typeof window !== "undefined") {
1694
+ window.addEventListener("pageshow", (e) => {
1695
+ if (e.persisted) hardNavigating = false;
1696
+ });
1697
+ }
1698
+ function fromPath() {
1699
+ return typeof window === "undefined" ? "" : window.location.pathname;
1700
+ }
1701
+ __name(fromPath, "fromPath");
1456
1702
  function getBasePath() {
1457
1703
  if (typeof process === "undefined") {
1458
1704
  return "";
@@ -1473,6 +1719,7 @@ function useCfgRouter() {
1473
1719
  const router = (0, import_navigation.useRouter)();
1474
1720
  const basePath = (0, import_react3.useMemo)(() => getBasePath(), []);
1475
1721
  const push = (0, import_react3.useCallback)((href, options) => {
1722
+ authTrace("nav.push", { from: fromPath(), to: href });
1476
1723
  if (basePath) {
1477
1724
  window.location.href = withBasePath(href, basePath);
1478
1725
  } else {
@@ -1480,6 +1727,7 @@ function useCfgRouter() {
1480
1727
  }
1481
1728
  }, [router, basePath]);
1482
1729
  const replace = (0, import_react3.useCallback)((href, options) => {
1730
+ authTrace("nav.replace", { from: fromPath(), to: href });
1483
1731
  if (basePath) {
1484
1732
  window.location.replace(withBasePath(href, basePath));
1485
1733
  } else {
@@ -1487,9 +1735,21 @@ function useCfgRouter() {
1487
1735
  }
1488
1736
  }, [router, basePath]);
1489
1737
  const hardPush = (0, import_react3.useCallback)((href) => {
1738
+ if (hardNavigating) {
1739
+ authTrace("nav.hardPush.ignored", { from: fromPath(), to: href });
1740
+ return;
1741
+ }
1742
+ hardNavigating = true;
1743
+ authTrace("nav.hardPush", { from: fromPath(), to: href });
1490
1744
  window.location.href = withBasePath(href, basePath);
1491
1745
  }, [basePath]);
1492
1746
  const hardReplace = (0, import_react3.useCallback)((href) => {
1747
+ if (hardNavigating) {
1748
+ authTrace("nav.hardReplace.ignored", { from: fromPath(), to: href });
1749
+ return;
1750
+ }
1751
+ hardNavigating = true;
1752
+ authTrace("nav.hardReplace", { from: fromPath(), to: href });
1493
1753
  window.location.replace(withBasePath(href, basePath));
1494
1754
  }, [basePath]);
1495
1755
  const prefetch = (0, import_react3.useCallback)((href) => {
@@ -1501,7 +1761,7 @@ function useCfgRouter() {
1501
1761
  const forward = (0, import_react3.useCallback)(() => {
1502
1762
  router.forward();
1503
1763
  }, [router]);
1504
- const refresh = (0, import_react3.useCallback)(() => {
1764
+ const refresh2 = (0, import_react3.useCallback)(() => {
1505
1765
  router.refresh();
1506
1766
  }, [router]);
1507
1767
  return {
@@ -1512,7 +1772,7 @@ function useCfgRouter() {
1512
1772
  prefetch,
1513
1773
  back,
1514
1774
  forward,
1515
- refresh
1775
+ refresh: refresh2
1516
1776
  };
1517
1777
  }
1518
1778
  __name(useCfgRouter, "useCfgRouter");
@@ -3455,7 +3715,7 @@ var OAuthErrorCodeEnum = /* @__PURE__ */ ((OAuthErrorCodeEnum2) => {
3455
3715
 
3456
3716
  // src/auth/utils/errors.ts
3457
3717
  var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
3458
- var TRANSPORT_FALLBACK = /^HTTP \d{3}:\s*/;
3718
+ var TRANSPORT_FALLBACK = /^HTTP \d{1,3}:\s*/;
3459
3719
  var AUTH_ERROR_KEYS = {
3460
3720
  invalidCredentials: "ui.errors.unauthorized",
3461
3721
  rateLimited: "api.errors.rateLimited",
@@ -3472,6 +3732,7 @@ var FALLBACK_COPY = {
3472
3732
  };
3473
3733
  var keyForStatus = /* @__PURE__ */ __name((status) => {
3474
3734
  if (status === void 0) return AUTH_ERROR_KEYS.generic;
3735
+ if (status === 0) return AUTH_ERROR_KEYS.networkError;
3475
3736
  if (status === 401 || status === 403) return AUTH_ERROR_KEYS.invalidCredentials;
3476
3737
  if (status === 404) return AUTH_ERROR_KEYS.serverUnavailable;
3477
3738
  if (status === 429) return AUTH_ERROR_KEYS.rateLimited;
@@ -3516,9 +3777,9 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
3516
3777
 
3517
3778
  // src/auth/utils/oauthErrors.ts
3518
3779
  var SERVER_CODES = new Set(Object.values(OAuthErrorCodeEnum));
3519
- var record = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3780
+ var record2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3520
3781
  var readOAuthFailure = /* @__PURE__ */ __name((err, fallback) => {
3521
- const body = record(record(err)?.response);
3782
+ const body = record2(record2(err)?.response);
3522
3783
  const code = body?.error;
3523
3784
  if (typeof code === "string" && SERVER_CODES.has(code)) {
3524
3785
  const description = body?.error_description;
@@ -3797,6 +4058,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3797
4058
  setLoadingProvider(provider);
3798
4059
  setError(null);
3799
4060
  setErrorCode(null);
4061
+ authTrace("oauth.start");
3800
4062
  auth.clearSession();
3801
4063
  try {
3802
4064
  Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
@@ -3844,6 +4106,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3844
4106
  if (!response.access || !response.refresh) {
3845
4107
  throw new Error("Invalid response from OAuth callback");
3846
4108
  }
4109
+ authTrace("oauth.session");
3847
4110
  auth.setSession({ access: response.access, refresh: response.refresh });
3848
4111
  Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
3849
4112
  category: "auth" /* AUTH */,
@@ -4024,6 +4287,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
4024
4287
  setError(null);
4025
4288
  }, []);
4026
4289
  const handleSuccess = (0, import_react14.useCallback)((response) => {
4290
+ authTrace("2fa.session");
4027
4291
  auth.setSession({ access: response.access_token, refresh: response.refresh_token });
4028
4292
  if (response.warning) {
4029
4293
  setWarning(response.warning);
@@ -4675,8 +4939,19 @@ function purgeLegacyTokenStorage() {
4675
4939
  }
4676
4940
  __name(purgeLegacyTokenStorage, "purgeLegacyTokenStorage");
4677
4941
 
4942
+ // src/auth/utils/authState.ts
4943
+ function resolveAuthStatus(s) {
4944
+ if (!s.clientReady) return "resolving";
4945
+ if (s.snapshotAuthenticated) {
4946
+ return s.profileSettled || s.timedOut ? "authenticated" : "resolving";
4947
+ }
4948
+ if (s.storeAuthenticated && !s.timedOut) return "resolving";
4949
+ return "anonymous";
4950
+ }
4951
+ __name(resolveAuthStatus, "resolveAuthStatus");
4952
+
4678
4953
  // src/auth/context/AccountsContext.tsx
4679
- var import_react19 = require("react");
4954
+ var import_react20 = require("react");
4680
4955
 
4681
4956
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsApiKeyRegenerateCreate.ts
4682
4957
  var import_mutation = __toESM(require("swr/mutation"), 1);
@@ -5023,6 +5298,15 @@ __name(useCfgAccountsTokenCreate, "useCfgAccountsTokenCreate");
5023
5298
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
5024
5299
  var import_mutation19 = __toESM(require("swr/mutation"), 1);
5025
5300
 
5301
+ // src/auth/context/sharedContext.ts
5302
+ var import_react19 = require("react");
5303
+ function sharedContext(key, initial) {
5304
+ const g = globalThis;
5305
+ const registry = g.__cfgAuthContexts ??= {};
5306
+ return registry[key] ??= (0, import_react19.createContext)(initial);
5307
+ }
5308
+ __name(sharedContext, "sharedContext");
5309
+
5026
5310
  // src/auth/context/AccountsContext.tsx
5027
5311
  var import_jsx_runtime = require("react/jsx-runtime");
5028
5312
  function hasApiOrigin() {
@@ -5036,9 +5320,9 @@ function hasApiOrigin() {
5036
5320
  }
5037
5321
  }
5038
5322
  __name(hasApiOrigin, "hasApiOrigin");
5039
- var AccountsContext = (0, import_react19.createContext)(void 0);
5323
+ var AccountsContext = sharedContext("accounts", void 0);
5040
5324
  function AccountsProvider({ children }) {
5041
- const [profile, setProfile] = (0, import_react19.useState)(() => {
5325
+ const [profile, setProfile] = (0, import_react20.useState)(() => {
5042
5326
  if (!auth.isAuthenticated()) {
5043
5327
  clearProfileCache();
5044
5328
  return void 0;
@@ -5046,15 +5330,15 @@ function AccountsProvider({ children }) {
5046
5330
  const cached = getCachedProfile();
5047
5331
  return cached || void 0;
5048
5332
  });
5049
- const [isLoadingProfile, setIsLoadingProfile] = (0, import_react19.useState)(false);
5050
- const [profileError, setProfileError] = (0, import_react19.useState)(null);
5051
- const profileRef = (0, import_react19.useRef)(profile);
5052
- const isLoadingRef = (0, import_react19.useRef)(false);
5053
- const fetchSeqRef = (0, import_react19.useRef)(0);
5054
- (0, import_react19.useEffect)(() => {
5333
+ const [isLoadingProfile, setIsLoadingProfile] = (0, import_react20.useState)(false);
5334
+ const [profileError, setProfileError] = (0, import_react20.useState)(null);
5335
+ const profileRef = (0, import_react20.useRef)(profile);
5336
+ const isLoadingRef = (0, import_react20.useRef)(false);
5337
+ const fetchSeqRef = (0, import_react20.useRef)(0);
5338
+ (0, import_react20.useEffect)(() => {
5055
5339
  profileRef.current = profile;
5056
5340
  }, [profile]);
5057
- (0, import_react19.useEffect)(() => {
5341
+ (0, import_react20.useEffect)(() => {
5058
5342
  isLoadingRef.current = isLoadingProfile;
5059
5343
  }, [isLoadingProfile]);
5060
5344
  const { trigger: triggerProfileUpdate } = useCfgAccountsProfileUpdateUpdate();
@@ -5063,7 +5347,7 @@ function AccountsProvider({ children }) {
5063
5347
  const { trigger: triggerOtpRequest } = useCfgAccountsOtpRequestCreate();
5064
5348
  const { trigger: triggerOtpVerify } = useCfgAccountsOtpVerifyCreate();
5065
5349
  const { trigger: triggerPasswordLogin } = useCfgAccountsTokenCreate();
5066
- const refreshProfile = (0, import_react19.useCallback)(async (options) => {
5350
+ const refreshProfile = (0, import_react20.useCallback)(async (options) => {
5067
5351
  const { callerId, force } = options || {};
5068
5352
  if (!hasApiOrigin()) {
5069
5353
  authLogger.debug(`No API origin configured, skipping profile fetch (caller: ${callerId})`);
@@ -5097,31 +5381,32 @@ function AccountsProvider({ children }) {
5097
5381
  }
5098
5382
  }
5099
5383
  }, []);
5100
- const updateProfile = (0, import_react19.useCallback)(async (data) => {
5384
+ const updateProfile = (0, import_react20.useCallback)(async (data) => {
5101
5385
  await triggerProfileUpdate({ body: data });
5102
5386
  const user = await refreshProfile({ callerId: "updateProfile", force: true });
5103
5387
  return user;
5104
5388
  }, [triggerProfileUpdate, refreshProfile]);
5105
- const partialUpdateProfile = (0, import_react19.useCallback)(async (data) => {
5389
+ const partialUpdateProfile = (0, import_react20.useCallback)(async (data) => {
5106
5390
  await triggerProfilePartialUpdate({ body: data });
5107
5391
  const user = await refreshProfile({ callerId: "partialUpdateProfile", force: true });
5108
5392
  return user;
5109
5393
  }, [triggerProfilePartialUpdate, refreshProfile]);
5110
- const uploadAvatar = (0, import_react19.useCallback)(async (avatar) => {
5394
+ const uploadAvatar = (0, import_react20.useCallback)(async (avatar) => {
5111
5395
  await triggerAvatar({ body: { avatar } });
5112
5396
  const user = await refreshProfile({ callerId: "uploadAvatar", force: true });
5113
5397
  return user;
5114
5398
  }, [triggerAvatar, refreshProfile]);
5115
- const requestOTP = (0, import_react19.useCallback)(async (data) => {
5399
+ const requestOTP = (0, import_react20.useCallback)(async (data) => {
5116
5400
  const result = await triggerOtpRequest({ body: data });
5117
5401
  return result;
5118
5402
  }, [triggerOtpRequest]);
5119
- const settleLoginEnvelope = (0, import_react19.useCallback)(async (result, callerId) => {
5403
+ const settleLoginEnvelope = (0, import_react20.useCallback)(async (result, callerId) => {
5120
5404
  if (result.requires_2fa && result.session_id) {
5121
5405
  authLogger.info("2FA required, session:", result.session_id);
5122
5406
  return;
5123
5407
  }
5124
5408
  if (result.access && result.refresh) {
5409
+ authTrace("accounts.otp.session");
5125
5410
  auth.setSession({ access: result.access, refresh: result.refresh });
5126
5411
  try {
5127
5412
  await refreshProfile({ callerId, force: true });
@@ -5130,29 +5415,30 @@ function AccountsProvider({ children }) {
5130
5415
  }
5131
5416
  }
5132
5417
  }, [refreshProfile]);
5133
- const verifyOTP = (0, import_react19.useCallback)(async (data) => {
5418
+ const verifyOTP = (0, import_react20.useCallback)(async (data) => {
5134
5419
  const result = await triggerOtpVerify({ body: data });
5135
5420
  await settleLoginEnvelope(result, "verifyOTP");
5136
5421
  return result;
5137
5422
  }, [triggerOtpVerify, settleLoginEnvelope]);
5138
- const loginWithPassword = (0, import_react19.useCallback)(async (data) => {
5423
+ const loginWithPassword = (0, import_react20.useCallback)(async (data) => {
5139
5424
  const result = await triggerPasswordLogin({ body: data });
5140
5425
  await settleLoginEnvelope(result, "loginWithPassword");
5141
5426
  return result;
5142
5427
  }, [triggerPasswordLogin, settleLoginEnvelope]);
5143
- const logout = (0, import_react19.useCallback)(() => {
5144
- const refresh = auth.getRefreshToken();
5145
- if (refresh) {
5146
- CfgAccountsAuth.cfgAccountsTokenBlacklistCreate({ body: { refresh } }).catch(() => {
5428
+ const logout = (0, import_react20.useCallback)(() => {
5429
+ const refresh2 = auth.getRefreshToken();
5430
+ if (refresh2) {
5431
+ CfgAccountsAuth.cfgAccountsTokenBlacklistCreate({ body: { refresh: refresh2 } }).catch(() => {
5147
5432
  authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
5148
5433
  });
5149
5434
  }
5435
+ authTrace("accounts.logout");
5150
5436
  auth.clearSession();
5151
5437
  setProfile(void 0);
5152
5438
  setProfileError(null);
5153
5439
  clearProfileCache();
5154
5440
  }, []);
5155
- const value = (0, import_react19.useMemo)(() => ({
5441
+ const value = (0, import_react20.useMemo)(() => ({
5156
5442
  profile,
5157
5443
  isLoadingProfile,
5158
5444
  profileError,
@@ -5181,11 +5467,11 @@ function AccountsProvider({ children }) {
5181
5467
  }
5182
5468
  __name(AccountsProvider, "AccountsProvider");
5183
5469
  function useAccountsContext() {
5184
- const context = (0, import_react19.useContext)(AccountsContext);
5185
- if (!context) {
5470
+ const context2 = (0, import_react20.useContext)(AccountsContext);
5471
+ if (!context2) {
5186
5472
  throw new Error("useAccountsContext must be used within AccountsProvider");
5187
5473
  }
5188
- return context;
5474
+ return context2;
5189
5475
  }
5190
5476
  __name(useAccountsContext, "useAccountsContext");
5191
5477
 
@@ -5202,7 +5488,7 @@ var resolveAuthRoutes = /* @__PURE__ */ __name((routes) => ({
5202
5488
  defaultAuthCallback: routes?.defaultAuthCallback || routes?.auth || defaultRoutes.defaultAuthCallback
5203
5489
  }), "resolveAuthRoutes");
5204
5490
  var EMAIL_STORAGE_KEY = CFG_KEYS.email.key;
5205
- var AuthContext = (0, import_react20.createContext)(void 0);
5491
+ var AuthContext = sharedContext("auth", void 0);
5206
5492
  var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5207
5493
  const accounts = useAccountsContext();
5208
5494
  const session = useSession();
@@ -5214,41 +5500,42 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5214
5500
  EMAIL_STORAGE_KEY,
5215
5501
  null
5216
5502
  );
5217
- const routes = (0, import_react20.useMemo)(() => resolveAuthRoutes(config?.routes), [config?.routes]);
5218
- const routesRef = (0, import_react20.useRef)(routes);
5219
- (0, import_react20.useEffect)(() => {
5503
+ const routes = (0, import_react21.useMemo)(() => resolveAuthRoutes(config?.routes), [config?.routes]);
5504
+ const routesRef = (0, import_react21.useRef)(routes);
5505
+ (0, import_react21.useEffect)(() => {
5220
5506
  routesRef.current = routes;
5221
5507
  }, [routes]);
5222
5508
  const redirectManager = useAuthRedirectManager({
5223
5509
  fallbackUrl: routes.defaultCallback,
5224
5510
  clearOnUse: true
5225
5511
  });
5226
- const configRef = (0, import_react20.useRef)(config);
5227
- (0, import_react20.useEffect)(() => {
5512
+ const configRef = (0, import_react21.useRef)(config);
5513
+ (0, import_react21.useEffect)(() => {
5228
5514
  configRef.current = config;
5229
5515
  }, [config]);
5230
- const accountsRef = (0, import_react20.useRef)(accounts);
5231
- (0, import_react20.useEffect)(() => {
5516
+ const accountsRef = (0, import_react21.useRef)(accounts);
5517
+ (0, import_react21.useEffect)(() => {
5232
5518
  accountsRef.current = accounts;
5233
5519
  }, [accounts]);
5234
- const pendingManualNavRef = (0, import_react20.useRef)(false);
5235
- const [profileSettled, setProfileSettled] = (0, import_react20.useState)(false);
5520
+ const pendingManualNavRef = (0, import_react21.useRef)(false);
5521
+ const [profileSettled, setProfileSettled] = (0, import_react21.useState)(false);
5236
5522
  const hasProfile = Boolean(accounts.profile);
5237
- const profileAttemptedRef = (0, import_react20.useRef)(false);
5238
- (0, import_react20.useEffect)(() => {
5523
+ const profileAttemptedRef = (0, import_react21.useRef)(false);
5524
+ (0, import_react21.useEffect)(() => {
5239
5525
  if (!isAuthenticated) {
5240
5526
  profileAttemptedRef.current = false;
5241
5527
  const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
5242
5528
  if (hasTokens && !auth.isAuthenticated()) {
5243
5529
  authLogger.warn("Dead session in storage \u2014 clearing");
5530
+ authTrace("bootstrap.purge", { why: "dead-tokens" });
5244
5531
  auth.clearSession();
5245
5532
  clearProfileCache();
5246
5533
  } else if (!hasTokens && !auth.isAuthenticated()) {
5534
+ authTrace("bootstrap.purge", { why: "no-session" });
5247
5535
  clearProfileCache();
5248
5536
  purgeLegacyTokenStorage();
5249
5537
  if (accountsRef.current.profile) accountsRef.current.logout();
5250
5538
  }
5251
- if (auth.isAuthenticated()) return;
5252
5539
  setProfileSettled(true);
5253
5540
  return;
5254
5541
  }
@@ -5257,9 +5544,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5257
5544
  return;
5258
5545
  }
5259
5546
  profileAttemptedRef.current = true;
5547
+ authTrace("bootstrap.profile.start");
5260
5548
  let cancelled = false;
5261
5549
  accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
5262
5550
  authLogger.warn("Profile load failed (session kept):", error);
5551
+ authTrace("bootstrap.profile.failed", {
5552
+ status: error instanceof APIError ? error.statusCode : 0
5553
+ });
5263
5554
  }).finally(() => {
5264
5555
  if (!cancelled) setProfileSettled(true);
5265
5556
  });
@@ -5268,11 +5559,47 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5268
5559
  };
5269
5560
  }, [isAuthenticated, hasProfile]);
5270
5561
  const isLoading = !profileSettled;
5271
- const expiredHandledRef = (0, import_react20.useRef)(false);
5272
- (0, import_react20.useEffect)(() => {
5562
+ const [clientReady, setClientReady] = (0, import_react21.useState)(false);
5563
+ const [timedOut, setTimedOut] = (0, import_react21.useState)(false);
5564
+ (0, import_react21.useEffect)(() => {
5565
+ setClientReady(true);
5566
+ const timer = setTimeout(() => setTimedOut(true), 8e3);
5567
+ return () => clearTimeout(timer);
5568
+ }, []);
5569
+ const status = resolveAuthStatus({
5570
+ clientReady,
5571
+ snapshotAuthenticated: isAuthenticated,
5572
+ storeAuthenticated: isBrowser2 ? auth.isAuthenticated() : false,
5573
+ profileSettled,
5574
+ timedOut
5575
+ });
5576
+ const lastStatusRef = (0, import_react21.useRef)("");
5577
+ (0, import_react21.useEffect)(() => {
5578
+ if (lastStatusRef.current === status) return;
5579
+ lastStatusRef.current = status;
5580
+ authTrace("status", {
5581
+ status,
5582
+ snapshot: isAuthenticated,
5583
+ store: auth.isAuthenticated(),
5584
+ profileSettled,
5585
+ timedOut,
5586
+ path: pathname ?? ""
5587
+ });
5588
+ }, [status, isAuthenticated, profileSettled, timedOut, pathname]);
5589
+ (0, import_react21.useEffect)(() => {
5590
+ globalThis.__cfgAuthNow = () => ({
5591
+ status,
5592
+ snapshot: isAuthenticated,
5593
+ store: auth.isAuthenticated(),
5594
+ hasToken: Boolean(auth.getToken()),
5595
+ hasRefresh: Boolean(auth.getRefreshToken()),
5596
+ profileSettled,
5597
+ timedOut
5598
+ });
5599
+ }, [status, isAuthenticated, profileSettled, timedOut]);
5600
+ (0, import_react21.useEffect)(() => {
5273
5601
  const unsubscribe = auth.onSessionExpired(() => {
5274
- if (expiredHandledRef.current) return;
5275
- expiredHandledRef.current = true;
5602
+ authTrace("expired.handler", { path: pathname ?? "" });
5276
5603
  authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
5277
5604
  clearProfileCache();
5278
5605
  Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
@@ -5281,16 +5608,17 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5281
5608
  router.hardReplace(routesRef.current.defaultAuthCallback);
5282
5609
  });
5283
5610
  return unsubscribe;
5284
- }, [router]);
5285
- (0, import_react20.useEffect)(() => {
5611
+ }, [router, pathname]);
5612
+ (0, import_react21.useEffect)(() => {
5286
5613
  if (!isAuthenticated || pendingManualNavRef.current) return;
5287
5614
  if (pathname === routes.auth && !queryParams.get("flow")) {
5288
5615
  const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
5289
5616
  if (saved) redirectManager.clearRedirect();
5617
+ authTrace("leave-auth", { to: saved || routes.defaultCallback, saved: Boolean(saved) });
5290
5618
  router.push(saved || routes.defaultCallback);
5291
5619
  }
5292
5620
  }, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
5293
- const loadCurrentProfile = (0, import_react20.useCallback)(async (callerId) => {
5621
+ const loadCurrentProfile = (0, import_react21.useCallback)(async (callerId) => {
5294
5622
  if (!auth.isAuthenticated()) {
5295
5623
  throw new Error("No valid authentication token");
5296
5624
  }
@@ -5298,7 +5626,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5298
5626
  callerId: callerId || "AuthContext.loadCurrentProfile"
5299
5627
  });
5300
5628
  }, []);
5301
- const checkAuthAndRedirect = (0, import_react20.useCallback)(async () => {
5629
+ const checkAuthAndRedirect = (0, import_react21.useCallback)(async () => {
5302
5630
  if (auth.isAuthenticated()) {
5303
5631
  try {
5304
5632
  await loadCurrentProfile("checkAuthAndRedirect");
@@ -5310,8 +5638,9 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5310
5638
  router.push(routesRef.current.defaultAuthCallback);
5311
5639
  }
5312
5640
  }, [loadCurrentProfile, router]);
5313
- const requestOTP = (0, import_react20.useCallback)(
5641
+ const requestOTP = (0, import_react21.useCallback)(
5314
5642
  async (identifier, sourceUrl, consent) => {
5643
+ authTrace("otp.request");
5315
5644
  auth.clearSession();
5316
5645
  try {
5317
5646
  const body = {
@@ -5352,10 +5681,11 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5352
5681
  },
5353
5682
  []
5354
5683
  );
5355
- const verifyOTP = (0, import_react20.useCallback)(
5684
+ const verifyOTP = (0, import_react21.useCallback)(
5356
5685
  async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
5357
5686
  try {
5358
5687
  pendingManualNavRef.current = Boolean(skipRedirect);
5688
+ authTrace("otp.verify", { skipRedirect: Boolean(skipRedirect) });
5359
5689
  const result = await accountsRef.current.verifyOTP({
5360
5690
  identifier,
5361
5691
  otp: otpCode,
@@ -5412,11 +5742,12 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5412
5742
  },
5413
5743
  [setStoredEmail, redirectManager, router]
5414
5744
  );
5415
- const login = (0, import_react20.useCallback)(
5745
+ const login = (0, import_react21.useCallback)(
5416
5746
  async (email, password, options) => {
5417
5747
  const { redirectUrl, skipRedirect } = options || {};
5418
5748
  try {
5419
5749
  pendingManualNavRef.current = Boolean(skipRedirect);
5750
+ authTrace("password.login", { skipRedirect: Boolean(skipRedirect) });
5420
5751
  const result = await accountsRef.current.loginWithPassword({
5421
5752
  email,
5422
5753
  password
@@ -5477,7 +5808,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5477
5808
  },
5478
5809
  [setStoredEmail, redirectManager, router]
5479
5810
  );
5480
- const refreshToken = (0, import_react20.useCallback)(async () => {
5811
+ const refreshToken = (0, import_react21.useCallback)(async () => {
5481
5812
  const access = await auth.refreshNow();
5482
5813
  if (access) {
5483
5814
  Analytics.event("auth_token_refresh" /* AUTH_TOKEN_REFRESH */, {
@@ -5490,7 +5821,8 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5490
5821
  });
5491
5822
  return { success: false, message: "Error refreshing token" };
5492
5823
  }, []);
5493
- const logout = (0, import_react20.useCallback)(() => {
5824
+ const logout = (0, import_react21.useCallback)(() => {
5825
+ authTrace("logout");
5494
5826
  Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
5495
5827
  category: "auth" /* AUTH */
5496
5828
  });
@@ -5499,24 +5831,25 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5499
5831
  }, [router]);
5500
5832
  const user = accounts.profile ?? null;
5501
5833
  const isAdminUser = Boolean(user?.is_staff || user?.is_superuser);
5502
- (0, import_react20.useEffect)(() => {
5834
+ (0, import_react21.useEffect)(() => {
5503
5835
  applyRoleLogPolicy({ isAdmin: isAdminUser });
5504
5836
  }, [isAdminUser]);
5505
- const updateProfile = (0, import_react20.useCallback)(
5837
+ const updateProfile = (0, import_react21.useCallback)(
5506
5838
  async (data) => {
5507
5839
  const result = await accountsRef.current.partialUpdateProfile(data);
5508
5840
  return result;
5509
5841
  },
5510
5842
  []
5511
5843
  );
5512
- const uploadAvatar = (0, import_react20.useCallback)(async (avatar) => {
5844
+ const uploadAvatar = (0, import_react21.useCallback)(async (avatar) => {
5513
5845
  const result = await accountsRef.current.uploadAvatar(avatar);
5514
5846
  return result;
5515
5847
  }, []);
5516
- const value = (0, import_react20.useMemo)(
5848
+ const value = (0, import_react21.useMemo)(
5517
5849
  () => ({
5518
5850
  user,
5519
5851
  isLoading,
5852
+ status,
5520
5853
  // Reactive: flips via the store snapshot (token writes, refreshes,
5521
5854
  // expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
5522
5855
  isAuthenticated,
@@ -5544,6 +5877,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5544
5877
  [
5545
5878
  user,
5546
5879
  isLoading,
5880
+ status,
5547
5881
  isAuthenticated,
5548
5882
  isAdminUser,
5549
5883
  routes,
@@ -5569,10 +5903,11 @@ function AuthProviderRaw({ children, config, enabled = true }) {
5569
5903
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AccountsProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthProviderInternal, { config, children }) });
5570
5904
  }
5571
5905
  __name(AuthProviderRaw, "AuthProviderRaw");
5572
- var AuthProvider = (0, import_react20.memo)(AuthProviderRaw);
5906
+ var AuthProvider = (0, import_react21.memo)(AuthProviderRaw);
5573
5907
  var defaultAuthState = {
5574
5908
  user: null,
5575
5909
  isLoading: false,
5910
+ status: "anonymous",
5576
5911
  isAuthenticated: false,
5577
5912
  isAdminUser: false,
5578
5913
  routes: resolveAuthRoutes(),
@@ -5625,14 +5960,14 @@ var defaultAuthState = {
5625
5960
  }, "uploadAvatar")
5626
5961
  };
5627
5962
  var useAuth = /* @__PURE__ */ __name(() => {
5628
- const context = (0, import_react20.useContext)(AuthContext);
5629
- if (context === void 0) {
5963
+ const context2 = (0, import_react21.useContext)(AuthContext);
5964
+ if (context2 === void 0) {
5630
5965
  if (isBrowser2 && isDev) {
5631
5966
  authLogger.debug("useAuth called outside AuthProvider, returning default state");
5632
5967
  }
5633
5968
  return defaultAuthState;
5634
5969
  }
5635
- return context;
5970
+ return context2;
5636
5971
  }, "useAuth");
5637
5972
  var AuthContext_default = AuthContext;
5638
5973