@djangocfg/api 2.1.618 → 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.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,10 @@ __export(auth_exports, {
234
238
  logger: () => logger,
235
239
  normalizePath: () => normalizePath,
236
240
  peekSavedRedirect: () => peekSavedRedirect,
241
+ readAuthTrace: () => readAuthTrace,
237
242
  readOAuthFailure: () => readOAuthFailure,
238
243
  resolveAuthError: () => resolveAuthError,
244
+ resolveAuthStatus: () => resolveAuthStatus,
239
245
  resolveGuardIsAuthenticated: () => resolveGuardIsAuthenticated,
240
246
  resolveGuardIsLoading: () => resolveGuardIsLoading,
241
247
  setAnalyticsSink: () => setAnalyticsSink,
@@ -281,6 +287,141 @@ var AUTH_CONSTANTS = {
281
287
  RESEND_COOLDOWN_SECONDS: 30
282
288
  };
283
289
 
290
+ // src/auth/trace.ts
291
+ var KEY = "cfg.auth.trace";
292
+ var MAX = 200;
293
+ var MAX_STRING = 120;
294
+ var SENSITIVE = /token|otp|code|email|password|secret|cookie|authorization|key|identifier/i;
295
+ var TAB = Math.random().toString(36).slice(2, 6);
296
+ var hasWindow = typeof window !== "undefined";
297
+ var memory = [];
298
+ var loaded = false;
299
+ function backend() {
300
+ if (!hasWindow) return null;
301
+ for (const name of ["localStorage", "sessionStorage"]) {
302
+ try {
303
+ const s = window[name];
304
+ s.setItem("__cfg_probe", "1");
305
+ s.removeItem("__cfg_probe");
306
+ return s;
307
+ } catch {
308
+ }
309
+ }
310
+ return null;
311
+ }
312
+ __name(backend, "backend");
313
+ function load() {
314
+ if (loaded) return memory;
315
+ loaded = true;
316
+ try {
317
+ const raw = backend()?.getItem(KEY);
318
+ const parsed = raw ? JSON.parse(raw) : [];
319
+ if (Array.isArray(parsed)) memory = parsed.filter((e) => typeof e === "object" && e !== null);
320
+ } catch {
321
+ memory = [];
322
+ }
323
+ return memory;
324
+ }
325
+ __name(load, "load");
326
+ function persist() {
327
+ try {
328
+ backend()?.setItem(KEY, JSON.stringify(memory));
329
+ } catch {
330
+ }
331
+ }
332
+ __name(persist, "persist");
333
+ function clean(fields) {
334
+ if (!fields) return void 0;
335
+ const out = {};
336
+ for (const [k2, v] of Object.entries(fields)) {
337
+ if (SENSITIVE.test(k2)) continue;
338
+ if (typeof v === "string") out[k2] = v.length > MAX_STRING ? `${v.slice(0, MAX_STRING)}\u2026` : v;
339
+ else if (typeof v === "number" || typeof v === "boolean" || v === null) out[k2] = v;
340
+ }
341
+ return Object.keys(out).length ? out : void 0;
342
+ }
343
+ __name(clean, "clean");
344
+ function record(at, ev, fields) {
345
+ const entries = load();
346
+ const p = hasWindow && typeof performance !== "undefined" ? Math.round(performance.now()) : 0;
347
+ entries.push({ t: at, p, tab: TAB, ev, f: clean(fields) });
348
+ if (entries.length > MAX) entries.splice(0, entries.length - MAX);
349
+ persist();
350
+ }
351
+ __name(record, "record");
352
+ function authTrace(ev, fields) {
353
+ if (!hasWindow) return;
354
+ try {
355
+ record(Date.now(), ev, fields);
356
+ } catch {
357
+ }
358
+ }
359
+ __name(authTrace, "authTrace");
360
+ function readAuthTrace() {
361
+ return load().slice();
362
+ }
363
+ __name(readAuthTrace, "readAuthTrace");
364
+ function clearAuthTrace() {
365
+ memory = [];
366
+ loaded = true;
367
+ try {
368
+ backend()?.removeItem(KEY);
369
+ } catch {
370
+ }
371
+ }
372
+ __name(clearAuthTrace, "clearAuthTrace");
373
+ function context() {
374
+ if (!hasWindow) return [];
375
+ const lines = [`url: ${window.location.href.split("?")[0]}`, `online: ${navigator.onLine}`, `ua: ${navigator.userAgent}`];
376
+ try {
377
+ const auth2 = globalThis.__cfgAuthNow;
378
+ const now = auth2?.();
379
+ if (now) lines.push(`now: ${Object.entries(now).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}`);
380
+ } catch {
381
+ }
382
+ return lines;
383
+ }
384
+ __name(context, "context");
385
+ function dumpAuthTrace() {
386
+ const entries = load();
387
+ const first = entries[0]?.t ?? Date.now();
388
+ const rows = entries.map((e) => {
389
+ const fields = e.f ? ` ${Object.entries(e.f).map(([k2, v]) => `${k2}=${String(v)}`).join(" ")}` : "";
390
+ const at = new Date(e.t).toISOString().slice(11, 23);
391
+ return `${at} +${String(e.t - first).padStart(6)}ms [${e.tab} p${e.p}] ${e.ev}${fields}`;
392
+ });
393
+ return [`# cfg auth trace, ${entries.length} events`, ...context(), "", ...rows].join("\n");
394
+ }
395
+ __name(dumpAuthTrace, "dumpAuthTrace");
396
+ function installAuthTrace() {
397
+ if (!hasWindow) return;
398
+ const g = globalThis;
399
+ g.__cfgAuthTrace = authTrace;
400
+ g.__cfgAuth = {
401
+ trace: readAuthTrace,
402
+ dump: /* @__PURE__ */ __name(() => {
403
+ const text = dumpAuthTrace();
404
+ console.log(text);
405
+ return text;
406
+ }, "dump"),
407
+ clear: clearAuthTrace
408
+ };
409
+ const queued = g.__cfgAuthTraceQ;
410
+ if (Array.isArray(queued)) {
411
+ g.__cfgAuthTraceQ = [];
412
+ for (const item of queued) {
413
+ if (Array.isArray(item) && typeof item[1] === "string") {
414
+ try {
415
+ record(Number(item[0]) || Date.now(), item[1], item[2]);
416
+ } catch {
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ __name(installAuthTrace, "installAuthTrace");
423
+ installAuthTrace();
424
+
284
425
  // src/auth/context/AuthContext.tsx
285
426
  var import_navigation4 = require("next/navigation");
286
427
  var import_react20 = require("react");
@@ -388,10 +529,10 @@ function normalizeSegment(raw) {
388
529
  return raw.trim().toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
389
530
  }
390
531
  __name(normalizeSegment, "normalizeSegment");
391
- var hasWindow = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
532
+ var hasWindow2 = /* @__PURE__ */ __name(() => typeof window !== "undefined", "hasWindow");
392
533
  var domainByHost = /* @__PURE__ */ new Map();
393
534
  function detectCookieDomain() {
394
- if (!hasWindow() || typeof document === "undefined") return null;
535
+ if (!hasWindow2() || typeof document === "undefined") return null;
395
536
  const host = window.location.hostname.toLowerCase();
396
537
  if (!host || !host.includes(".") || host.includes(":") || /^[0-9.]+$/.test(host)) return null;
397
538
  const cached = domainByHost.get(host);
@@ -415,7 +556,7 @@ function detectCookieDomain() {
415
556
  }
416
557
  __name(detectCookieDomain, "detectCookieDomain");
417
558
  function nativeStorage(store) {
418
- if (!hasWindow()) return null;
559
+ if (!hasWindow2()) return null;
419
560
  try {
420
561
  return store === "session" ? window.sessionStorage : window.localStorage;
421
562
  } catch {
@@ -424,7 +565,7 @@ function nativeStorage(store) {
424
565
  }
425
566
  __name(nativeStorage, "nativeStorage");
426
567
  function cookieNames() {
427
- if (!hasWindow()) return [];
568
+ if (!hasWindow2()) return [];
428
569
  try {
429
570
  return document.cookie.split("; ").map((part) => decodeURIComponent(part.split("=")[0] ?? "")).filter(Boolean);
430
571
  } catch {
@@ -449,7 +590,7 @@ function createStorage(owner, options = {}) {
449
590
  const families = Object.values(options.families ?? {});
450
591
  const specByKey = /* @__PURE__ */ new Map();
451
592
  for (const spec of specs) specByKey.set(spec.key, [...specByKey.get(spec.key) ?? [], spec]);
452
- const memory = {
593
+ const memory2 = {
453
594
  local: /* @__PURE__ */ new Map(),
454
595
  session: /* @__PURE__ */ new Map(),
455
596
  cookie: /* @__PURE__ */ new Map()
@@ -463,7 +604,7 @@ function createStorage(owner, options = {}) {
463
604
  if (!key.startsWith(prefix)) throw new TypeError(`storage key "${key}" is not owned by "${owner}"`);
464
605
  }, "assertOwned");
465
606
  function rawGet(store, key) {
466
- if (!hasWindow()) return null;
607
+ if (!hasWindow2()) return null;
467
608
  if (store === "cookie") {
468
609
  try {
469
610
  const escaped = encodeURIComponent(key).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -471,18 +612,18 @@ function createStorage(owner, options = {}) {
471
612
  if (match?.[1] !== void 0) return decodeURIComponent(match[1]);
472
613
  } catch {
473
614
  }
474
- return memory.cookie.get(key) ?? null;
615
+ return memory2.cookie.get(key) ?? null;
475
616
  }
476
617
  try {
477
618
  const value = nativeStorage(store)?.getItem(key) ?? null;
478
619
  if (value !== null) return value;
479
620
  } catch {
480
621
  }
481
- return memory[store].get(key) ?? null;
622
+ return memory2[store].get(key) ?? null;
482
623
  }
483
624
  __name(rawGet, "rawGet");
484
625
  function rawSet(store, key, value, maxAgeSeconds) {
485
- if (!hasWindow()) return false;
626
+ if (!hasWindow2()) return false;
486
627
  try {
487
628
  if (store === "cookie") {
488
629
  const secure = window.location.protocol === "https:" ? "; Secure" : "";
@@ -490,24 +631,24 @@ function createStorage(owner, options = {}) {
490
631
  const domainPart = domain ? `; Domain=${domain}` : "";
491
632
  document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; Path=/; Max-Age=${Math.max(1, Math.floor(maxAgeSeconds ?? cookieMaxAge))}; SameSite=Lax${domainPart}${secure}`;
492
633
  if (domain) expireCookie(key, null);
493
- if (rawGet("cookie", key) === value && !memory.cookie.has(key)) return true;
494
- memory.cookie.set(key, value);
634
+ if (rawGet("cookie", key) === value && !memory2.cookie.has(key)) return true;
635
+ memory2.cookie.set(key, value);
495
636
  return false;
496
637
  }
497
638
  const native = nativeStorage(store);
498
639
  if (!native) throw new Error("storage unavailable");
499
640
  native.setItem(key, value);
500
- memory[store].delete(key);
641
+ memory2[store].delete(key);
501
642
  return true;
502
643
  } catch {
503
- memory[store].set(key, value);
644
+ memory2[store].set(key, value);
504
645
  return false;
505
646
  }
506
647
  }
507
648
  __name(rawSet, "rawSet");
508
649
  function rawRemove(store, key) {
509
- memory[store].delete(key);
510
- if (!hasWindow()) return;
650
+ memory2[store].delete(key);
651
+ if (!hasWindow2()) return;
511
652
  try {
512
653
  if (store === "cookie") {
513
654
  expireCookie(key, null);
@@ -536,7 +677,7 @@ function createStorage(owner, options = {}) {
536
677
  }
537
678
  } catch {
538
679
  }
539
- for (const name of memory[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
680
+ for (const name of memory2[store].keys()) if (name.startsWith(startsWith) && !out.includes(name)) out.push(name);
540
681
  return out;
541
682
  }
542
683
  __name(listKeys, "listKeys");
@@ -608,7 +749,7 @@ function createStorage(owner, options = {}) {
608
749
  cookieDomain: resolveCookieDomain,
609
750
  onChange(key, listener, store = "local") {
610
751
  assertValid(key);
611
- if (!hasWindow()) return () => {
752
+ if (!hasWindow2()) return () => {
612
753
  };
613
754
  const handler = /* @__PURE__ */ __name((event) => {
614
755
  if (event.storageArea !== nativeStorage(store)) return;
@@ -632,6 +773,29 @@ var REFRESH_KEY = "cfg.auth.refresh_token";
632
773
  var API_KEY_KEY = "cfg.auth.api_key";
633
774
  var storage = true ? cfgStorage : createStorage2("cfg");
634
775
  var isBrowser = typeof window !== "undefined";
776
+ function trace(ev, fields) {
777
+ try {
778
+ const g = globalThis;
779
+ const sink2 = g.__cfgAuthTrace;
780
+ if (sink2) {
781
+ sink2(ev, fields);
782
+ return;
783
+ }
784
+ const queue = g.__cfgAuthTraceQ ?? [];
785
+ g.__cfgAuthTraceQ = queue;
786
+ if (queue.length < 100) queue.push([Date.now(), ev, fields]);
787
+ } catch {
788
+ }
789
+ }
790
+ __name(trace, "trace");
791
+ function pathOf(url) {
792
+ try {
793
+ return new URL(url, "http://x").pathname;
794
+ } catch {
795
+ return "";
796
+ }
797
+ }
798
+ __name(pathOf, "pathOf");
635
799
  function secondsUntilExp(token) {
636
800
  const exp = jwtExpMs(token);
637
801
  return exp === null ? void 0 : Math.max(1, Math.floor((exp - Date.now()) / 1e3));
@@ -791,7 +955,10 @@ __name(scheduleExpiryFlip, "scheduleExpiryFlip");
791
955
  function notifySessionChanged() {
792
956
  const next = computeSnapshot();
793
957
  const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
794
- if (changed) _snapshot = next;
958
+ if (changed) {
959
+ _snapshot = next;
960
+ trace("store.snapshot", { status: next.status });
961
+ }
795
962
  scheduleExpiryFlip();
796
963
  if (!changed) return;
797
964
  for (const listener of Array.from(_sessionListeners)) {
@@ -874,6 +1041,7 @@ var auth = {
874
1041
  return _storage.get(ACCESS_KEY);
875
1042
  },
876
1043
  setToken(token) {
1044
+ trace("store.setToken", { present: token !== null });
877
1045
  _storage.set(ACCESS_KEY, token);
878
1046
  notifySessionChanged();
879
1047
  },
@@ -881,14 +1049,16 @@ var auth = {
881
1049
  return _storage.get(REFRESH_KEY);
882
1050
  },
883
1051
  setRefreshToken(token) {
1052
+ trace("store.setRefreshToken", { present: token !== null });
884
1053
  _storage.set(REFRESH_KEY, token);
885
1054
  notifySessionChanged();
886
1055
  },
887
1056
  clearTokens() {
1057
+ trace("store.clearTokens");
888
1058
  for (const mode of ["localStorage", "sessionStorage", "cookie"]) {
889
- const backend = backendFor(mode);
890
- backend.set(ACCESS_KEY, null);
891
- backend.set(REFRESH_KEY, null);
1059
+ const backend2 = backendFor(mode);
1060
+ backend2.set(ACCESS_KEY, null);
1061
+ backend2.set(REFRESH_KEY, null);
892
1062
  }
893
1063
  storage.clearAll();
894
1064
  notifySessionChanged();
@@ -907,6 +1077,7 @@ var auth = {
907
1077
  * rotation); `refresh: null` explicitly drops it.
908
1078
  */
909
1079
  setSession(tokens) {
1080
+ trace("store.setSession", { refresh: tokens.refresh === void 0 ? "kept" : tokens.refresh === null ? "dropped" : "set" });
910
1081
  _storage.set(ACCESS_KEY, tokens.access);
911
1082
  if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
912
1083
  notifySessionChanged();
@@ -1045,7 +1216,11 @@ async function tryRefresh() {
1045
1216
  const refresh = auth.getRefreshToken();
1046
1217
  if (!refresh) return null;
1047
1218
  const result = await _refreshHandler(refresh);
1048
- if (!result?.access) return null;
1219
+ if (!result?.access) {
1220
+ trace("refresh", { ok: false, why: "no-access" });
1221
+ return null;
1222
+ }
1223
+ trace("refresh", { ok: true, rotated: Boolean(result.refresh) });
1049
1224
  auth.setToken(result.access);
1050
1225
  if (result.refresh) auth.setRefreshToken(result.refresh);
1051
1226
  return result.access;
@@ -1058,6 +1233,7 @@ async function tryRefresh() {
1058
1233
  }
1059
1234
  return await runRefresh();
1060
1235
  } catch {
1236
+ trace("refresh", { ok: false, why: "threw" });
1061
1237
  return null;
1062
1238
  } finally {
1063
1239
  _refreshInflight = null;
@@ -1066,7 +1242,8 @@ async function tryRefresh() {
1066
1242
  return _refreshInflight;
1067
1243
  }
1068
1244
  __name(tryRefresh, "tryRefresh");
1069
- function expireSession(response) {
1245
+ function expireSession(response, why) {
1246
+ trace("store.expire", { why, status: response.status, path: pathOf(response.url), handlers: _sessionExpiredHandlers.size });
1070
1247
  auth.clearTokens();
1071
1248
  for (const cb of Array.from(_sessionExpiredHandlers)) {
1072
1249
  try {
@@ -1208,13 +1385,17 @@ function installAuthOnClient(client2) {
1208
1385
  });
1209
1386
  client2.interceptors.response.use(async (response, request) => {
1210
1387
  if (response.status !== 401) return response;
1388
+ if (/\/accounts\/(otp|oauth|login|register|token)\b/.test(request.url)) {
1389
+ trace("401.passthrough", { path: pathOf(request.url) });
1390
+ return response;
1391
+ }
1211
1392
  if (request.headers.get(RETRY_MARKER)) {
1212
- expireSession(response);
1393
+ expireSession(response, "retry-marker");
1213
1394
  return response;
1214
1395
  }
1215
1396
  const newToken = await tryRefresh();
1216
1397
  if (!newToken) {
1217
- expireSession(response);
1398
+ expireSession(response, "refresh-failed");
1218
1399
  return response;
1219
1400
  }
1220
1401
  const retry = request.clone();
@@ -1226,7 +1407,7 @@ function installAuthOnClient(client2) {
1226
1407
  }
1227
1408
  try {
1228
1409
  const retried = await fetch(retry);
1229
- if (retried.status === 401) expireSession(retried);
1410
+ if (retried.status === 401) expireSession(retried, "retry-401");
1230
1411
  return retried;
1231
1412
  } catch {
1232
1413
  return response;
@@ -1450,6 +1631,16 @@ __name(useTimezone, "useTimezone");
1450
1631
  // src/auth/hooks/useCfgRouter.ts
1451
1632
  var import_navigation = require("next/navigation");
1452
1633
  var import_react3 = require("react");
1634
+ var hardNavigating = false;
1635
+ if (typeof window !== "undefined") {
1636
+ window.addEventListener("pageshow", (e) => {
1637
+ if (e.persisted) hardNavigating = false;
1638
+ });
1639
+ }
1640
+ function fromPath() {
1641
+ return typeof window === "undefined" ? "" : window.location.pathname;
1642
+ }
1643
+ __name(fromPath, "fromPath");
1453
1644
  function getBasePath() {
1454
1645
  if (typeof process === "undefined") {
1455
1646
  return "";
@@ -1470,6 +1661,7 @@ function useCfgRouter() {
1470
1661
  const router = (0, import_navigation.useRouter)();
1471
1662
  const basePath = (0, import_react3.useMemo)(() => getBasePath(), []);
1472
1663
  const push = (0, import_react3.useCallback)((href, options) => {
1664
+ authTrace("nav.push", { from: fromPath(), to: href });
1473
1665
  if (basePath) {
1474
1666
  window.location.href = withBasePath(href, basePath);
1475
1667
  } else {
@@ -1477,6 +1669,7 @@ function useCfgRouter() {
1477
1669
  }
1478
1670
  }, [router, basePath]);
1479
1671
  const replace = (0, import_react3.useCallback)((href, options) => {
1672
+ authTrace("nav.replace", { from: fromPath(), to: href });
1480
1673
  if (basePath) {
1481
1674
  window.location.replace(withBasePath(href, basePath));
1482
1675
  } else {
@@ -1484,9 +1677,21 @@ function useCfgRouter() {
1484
1677
  }
1485
1678
  }, [router, basePath]);
1486
1679
  const hardPush = (0, import_react3.useCallback)((href) => {
1680
+ if (hardNavigating) {
1681
+ authTrace("nav.hardPush.ignored", { from: fromPath(), to: href });
1682
+ return;
1683
+ }
1684
+ hardNavigating = true;
1685
+ authTrace("nav.hardPush", { from: fromPath(), to: href });
1487
1686
  window.location.href = withBasePath(href, basePath);
1488
1687
  }, [basePath]);
1489
1688
  const hardReplace = (0, import_react3.useCallback)((href) => {
1689
+ if (hardNavigating) {
1690
+ authTrace("nav.hardReplace.ignored", { from: fromPath(), to: href });
1691
+ return;
1692
+ }
1693
+ hardNavigating = true;
1694
+ authTrace("nav.hardReplace", { from: fromPath(), to: href });
1490
1695
  window.location.replace(withBasePath(href, basePath));
1491
1696
  }, [basePath]);
1492
1697
  const prefetch = (0, import_react3.useCallback)((href) => {
@@ -3452,7 +3657,7 @@ var OAuthErrorCodeEnum = /* @__PURE__ */ ((OAuthErrorCodeEnum2) => {
3452
3657
 
3453
3658
  // src/auth/utils/errors.ts
3454
3659
  var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
3455
- var TRANSPORT_FALLBACK = /^HTTP \d{3}:\s*/;
3660
+ var TRANSPORT_FALLBACK = /^HTTP \d{1,3}:\s*/;
3456
3661
  var AUTH_ERROR_KEYS = {
3457
3662
  invalidCredentials: "ui.errors.unauthorized",
3458
3663
  rateLimited: "api.errors.rateLimited",
@@ -3469,6 +3674,7 @@ var FALLBACK_COPY = {
3469
3674
  };
3470
3675
  var keyForStatus = /* @__PURE__ */ __name((status) => {
3471
3676
  if (status === void 0) return AUTH_ERROR_KEYS.generic;
3677
+ if (status === 0) return AUTH_ERROR_KEYS.networkError;
3472
3678
  if (status === 401 || status === 403) return AUTH_ERROR_KEYS.invalidCredentials;
3473
3679
  if (status === 404) return AUTH_ERROR_KEYS.serverUnavailable;
3474
3680
  if (status === 429) return AUTH_ERROR_KEYS.rateLimited;
@@ -3513,9 +3719,9 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
3513
3719
 
3514
3720
  // src/auth/utils/oauthErrors.ts
3515
3721
  var SERVER_CODES = new Set(Object.values(OAuthErrorCodeEnum));
3516
- var record = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3722
+ var record2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null ? value : null, "record");
3517
3723
  var readOAuthFailure = /* @__PURE__ */ __name((err, fallback) => {
3518
- const body = record(record(err)?.response);
3724
+ const body = record2(record2(err)?.response);
3519
3725
  const code = body?.error;
3520
3726
  if (typeof code === "string" && SERVER_CODES.has(code)) {
3521
3727
  const description = body?.error_description;
@@ -3794,6 +4000,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3794
4000
  setLoadingProvider(provider);
3795
4001
  setError(null);
3796
4002
  setErrorCode(null);
4003
+ authTrace("oauth.start");
3797
4004
  auth.clearSession();
3798
4005
  try {
3799
4006
  Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
@@ -3841,6 +4048,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3841
4048
  if (!response.access || !response.refresh) {
3842
4049
  throw new Error("Invalid response from OAuth callback");
3843
4050
  }
4051
+ authTrace("oauth.session");
3844
4052
  auth.setSession({ access: response.access, refresh: response.refresh });
3845
4053
  Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
3846
4054
  category: "auth" /* AUTH */,
@@ -4021,6 +4229,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
4021
4229
  setError(null);
4022
4230
  }, []);
4023
4231
  const handleSuccess = (0, import_react14.useCallback)((response) => {
4232
+ authTrace("2fa.session");
4024
4233
  auth.setSession({ access: response.access_token, refresh: response.refresh_token });
4025
4234
  if (response.warning) {
4026
4235
  setWarning(response.warning);
@@ -4672,6 +4881,17 @@ function purgeLegacyTokenStorage() {
4672
4881
  }
4673
4882
  __name(purgeLegacyTokenStorage, "purgeLegacyTokenStorage");
4674
4883
 
4884
+ // src/auth/utils/authState.ts
4885
+ function resolveAuthStatus(s) {
4886
+ if (!s.clientReady) return "resolving";
4887
+ if (s.snapshotAuthenticated) {
4888
+ return s.profileSettled || s.timedOut ? "authenticated" : "resolving";
4889
+ }
4890
+ if (s.storeAuthenticated && !s.timedOut) return "resolving";
4891
+ return "anonymous";
4892
+ }
4893
+ __name(resolveAuthStatus, "resolveAuthStatus");
4894
+
4675
4895
  // src/auth/context/AccountsContext.tsx
4676
4896
  var import_react19 = require("react");
4677
4897
 
@@ -5119,6 +5339,7 @@ function AccountsProvider({ children }) {
5119
5339
  return;
5120
5340
  }
5121
5341
  if (result.access && result.refresh) {
5342
+ authTrace("accounts.otp.session");
5122
5343
  auth.setSession({ access: result.access, refresh: result.refresh });
5123
5344
  try {
5124
5345
  await refreshProfile({ callerId, force: true });
@@ -5144,6 +5365,7 @@ function AccountsProvider({ children }) {
5144
5365
  authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
5145
5366
  });
5146
5367
  }
5368
+ authTrace("accounts.logout");
5147
5369
  auth.clearSession();
5148
5370
  setProfile(void 0);
5149
5371
  setProfileError(null);
@@ -5178,11 +5400,11 @@ function AccountsProvider({ children }) {
5178
5400
  }
5179
5401
  __name(AccountsProvider, "AccountsProvider");
5180
5402
  function useAccountsContext() {
5181
- const context = (0, import_react19.useContext)(AccountsContext);
5182
- if (!context) {
5403
+ const context2 = (0, import_react19.useContext)(AccountsContext);
5404
+ if (!context2) {
5183
5405
  throw new Error("useAccountsContext must be used within AccountsProvider");
5184
5406
  }
5185
- return context;
5407
+ return context2;
5186
5408
  }
5187
5409
  __name(useAccountsContext, "useAccountsContext");
5188
5410
 
@@ -5238,9 +5460,11 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5238
5460
  const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
5239
5461
  if (hasTokens && !auth.isAuthenticated()) {
5240
5462
  authLogger.warn("Dead session in storage \u2014 clearing");
5463
+ authTrace("bootstrap.purge", { why: "dead-tokens" });
5241
5464
  auth.clearSession();
5242
5465
  clearProfileCache();
5243
5466
  } else if (!hasTokens && !auth.isAuthenticated()) {
5467
+ authTrace("bootstrap.purge", { why: "no-session" });
5244
5468
  clearProfileCache();
5245
5469
  purgeLegacyTokenStorage();
5246
5470
  if (accountsRef.current.profile) accountsRef.current.logout();
@@ -5253,9 +5477,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5253
5477
  return;
5254
5478
  }
5255
5479
  profileAttemptedRef.current = true;
5480
+ authTrace("bootstrap.profile.start");
5256
5481
  let cancelled = false;
5257
5482
  accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
5258
5483
  authLogger.warn("Profile load failed (session kept):", error);
5484
+ authTrace("bootstrap.profile.failed", {
5485
+ status: error instanceof APIError ? error.statusCode : 0
5486
+ });
5259
5487
  }).finally(() => {
5260
5488
  if (!cancelled) setProfileSettled(true);
5261
5489
  });
@@ -5264,11 +5492,47 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5264
5492
  };
5265
5493
  }, [isAuthenticated, hasProfile]);
5266
5494
  const isLoading = !profileSettled;
5267
- const expiredHandledRef = (0, import_react20.useRef)(false);
5495
+ const [clientReady, setClientReady] = (0, import_react20.useState)(false);
5496
+ const [timedOut, setTimedOut] = (0, import_react20.useState)(false);
5497
+ (0, import_react20.useEffect)(() => {
5498
+ setClientReady(true);
5499
+ const timer = setTimeout(() => setTimedOut(true), 8e3);
5500
+ return () => clearTimeout(timer);
5501
+ }, []);
5502
+ const status = resolveAuthStatus({
5503
+ clientReady,
5504
+ snapshotAuthenticated: isAuthenticated,
5505
+ storeAuthenticated: isBrowser2 ? auth.isAuthenticated() : false,
5506
+ profileSettled,
5507
+ timedOut
5508
+ });
5509
+ const lastStatusRef = (0, import_react20.useRef)("");
5510
+ (0, import_react20.useEffect)(() => {
5511
+ if (lastStatusRef.current === status) return;
5512
+ lastStatusRef.current = status;
5513
+ authTrace("status", {
5514
+ status,
5515
+ snapshot: isAuthenticated,
5516
+ store: auth.isAuthenticated(),
5517
+ profileSettled,
5518
+ timedOut,
5519
+ path: pathname ?? ""
5520
+ });
5521
+ }, [status, isAuthenticated, profileSettled, timedOut, pathname]);
5522
+ (0, import_react20.useEffect)(() => {
5523
+ globalThis.__cfgAuthNow = () => ({
5524
+ status,
5525
+ snapshot: isAuthenticated,
5526
+ store: auth.isAuthenticated(),
5527
+ hasToken: Boolean(auth.getToken()),
5528
+ hasRefresh: Boolean(auth.getRefreshToken()),
5529
+ profileSettled,
5530
+ timedOut
5531
+ });
5532
+ }, [status, isAuthenticated, profileSettled, timedOut]);
5268
5533
  (0, import_react20.useEffect)(() => {
5269
5534
  const unsubscribe = auth.onSessionExpired(() => {
5270
- if (expiredHandledRef.current) return;
5271
- expiredHandledRef.current = true;
5535
+ authTrace("expired.handler", { path: pathname ?? "" });
5272
5536
  authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
5273
5537
  clearProfileCache();
5274
5538
  Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
@@ -5277,12 +5541,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5277
5541
  router.hardReplace(routesRef.current.defaultAuthCallback);
5278
5542
  });
5279
5543
  return unsubscribe;
5280
- }, [router]);
5544
+ }, [router, pathname]);
5281
5545
  (0, import_react20.useEffect)(() => {
5282
5546
  if (!isAuthenticated || pendingManualNavRef.current) return;
5283
5547
  if (pathname === routes.auth && !queryParams.get("flow")) {
5284
5548
  const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
5285
5549
  if (saved) redirectManager.clearRedirect();
5550
+ authTrace("leave-auth", { to: saved || routes.defaultCallback, saved: Boolean(saved) });
5286
5551
  router.push(saved || routes.defaultCallback);
5287
5552
  }
5288
5553
  }, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
@@ -5308,6 +5573,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5308
5573
  }, [loadCurrentProfile, router]);
5309
5574
  const requestOTP = (0, import_react20.useCallback)(
5310
5575
  async (identifier, sourceUrl, consent) => {
5576
+ authTrace("otp.request");
5311
5577
  auth.clearSession();
5312
5578
  try {
5313
5579
  const body = {
@@ -5352,6 +5618,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5352
5618
  async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
5353
5619
  try {
5354
5620
  pendingManualNavRef.current = Boolean(skipRedirect);
5621
+ authTrace("otp.verify", { skipRedirect: Boolean(skipRedirect) });
5355
5622
  const result = await accountsRef.current.verifyOTP({
5356
5623
  identifier,
5357
5624
  otp: otpCode,
@@ -5413,6 +5680,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5413
5680
  const { redirectUrl, skipRedirect } = options || {};
5414
5681
  try {
5415
5682
  pendingManualNavRef.current = Boolean(skipRedirect);
5683
+ authTrace("password.login", { skipRedirect: Boolean(skipRedirect) });
5416
5684
  const result = await accountsRef.current.loginWithPassword({
5417
5685
  email,
5418
5686
  password
@@ -5487,6 +5755,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5487
5755
  return { success: false, message: "Error refreshing token" };
5488
5756
  }, []);
5489
5757
  const logout = (0, import_react20.useCallback)(() => {
5758
+ authTrace("logout");
5490
5759
  Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
5491
5760
  category: "auth" /* AUTH */
5492
5761
  });
@@ -5513,6 +5782,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5513
5782
  () => ({
5514
5783
  user,
5515
5784
  isLoading,
5785
+ status,
5516
5786
  // Reactive: flips via the store snapshot (token writes, refreshes,
5517
5787
  // expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
5518
5788
  isAuthenticated,
@@ -5540,6 +5810,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5540
5810
  [
5541
5811
  user,
5542
5812
  isLoading,
5813
+ status,
5543
5814
  isAuthenticated,
5544
5815
  isAdminUser,
5545
5816
  routes,
@@ -5569,6 +5840,7 @@ var AuthProvider = (0, import_react20.memo)(AuthProviderRaw);
5569
5840
  var defaultAuthState = {
5570
5841
  user: null,
5571
5842
  isLoading: false,
5843
+ status: "anonymous",
5572
5844
  isAuthenticated: false,
5573
5845
  isAdminUser: false,
5574
5846
  routes: resolveAuthRoutes(),
@@ -5621,14 +5893,14 @@ var defaultAuthState = {
5621
5893
  }, "uploadAvatar")
5622
5894
  };
5623
5895
  var useAuth = /* @__PURE__ */ __name(() => {
5624
- const context = (0, import_react20.useContext)(AuthContext);
5625
- if (context === void 0) {
5896
+ const context2 = (0, import_react20.useContext)(AuthContext);
5897
+ if (context2 === void 0) {
5626
5898
  if (isBrowser2 && isDev) {
5627
5899
  authLogger.debug("useAuth called outside AuthProvider, returning default state");
5628
5900
  }
5629
5901
  return defaultAuthState;
5630
5902
  }
5631
- return context;
5903
+ return context2;
5632
5904
  }, "useAuth");
5633
5905
  var AuthContext_default = AuthContext;
5634
5906