@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.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 {
@@ -951,13 +1122,17 @@ function installAuthOnClient(client2) {
951
1122
  });
952
1123
  client2.interceptors.response.use(async (response, request) => {
953
1124
  if (response.status !== 401) return response;
1125
+ if (/\/accounts\/(otp|oauth|login|register|token)\b/.test(request.url)) {
1126
+ trace("401.passthrough", { path: pathOf(request.url) });
1127
+ return response;
1128
+ }
954
1129
  if (request.headers.get(RETRY_MARKER)) {
955
- expireSession(response);
1130
+ expireSession(response, "retry-marker");
956
1131
  return response;
957
1132
  }
958
1133
  const newToken = await tryRefresh();
959
1134
  if (!newToken) {
960
- expireSession(response);
1135
+ expireSession(response, "refresh-failed");
961
1136
  return response;
962
1137
  }
963
1138
  const retry = request.clone();
@@ -969,7 +1144,7 @@ function installAuthOnClient(client2) {
969
1144
  }
970
1145
  try {
971
1146
  const retried = await fetch(retry);
972
- if (retried.status === 401) expireSession(retried);
1147
+ if (retried.status === 401) expireSession(retried, "retry-401");
973
1148
  return retried;
974
1149
  } catch {
975
1150
  return response;
@@ -1193,6 +1368,16 @@ __name(useTimezone, "useTimezone");
1193
1368
  // src/auth/hooks/useCfgRouter.ts
1194
1369
  import { useRouter as useNextRouter } from "next/navigation";
1195
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");
1196
1381
  function getBasePath() {
1197
1382
  if (typeof process === "undefined") {
1198
1383
  return "";
@@ -1213,6 +1398,7 @@ function useCfgRouter() {
1213
1398
  const router = useNextRouter();
1214
1399
  const basePath = useMemo(() => getBasePath(), []);
1215
1400
  const push = useCallback2((href, options) => {
1401
+ authTrace("nav.push", { from: fromPath(), to: href });
1216
1402
  if (basePath) {
1217
1403
  window.location.href = withBasePath(href, basePath);
1218
1404
  } else {
@@ -1220,6 +1406,7 @@ function useCfgRouter() {
1220
1406
  }
1221
1407
  }, [router, basePath]);
1222
1408
  const replace = useCallback2((href, options) => {
1409
+ authTrace("nav.replace", { from: fromPath(), to: href });
1223
1410
  if (basePath) {
1224
1411
  window.location.replace(withBasePath(href, basePath));
1225
1412
  } else {
@@ -1227,9 +1414,21 @@ function useCfgRouter() {
1227
1414
  }
1228
1415
  }, [router, basePath]);
1229
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 });
1230
1423
  window.location.href = withBasePath(href, basePath);
1231
1424
  }, [basePath]);
1232
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 });
1233
1432
  window.location.replace(withBasePath(href, basePath));
1234
1433
  }, [basePath]);
1235
1434
  const prefetch = useCallback2((href) => {
@@ -3195,7 +3394,7 @@ var OAuthErrorCodeEnum = /* @__PURE__ */ ((OAuthErrorCodeEnum2) => {
3195
3394
 
3196
3395
  // src/auth/utils/errors.ts
3197
3396
  var prop = /* @__PURE__ */ __name((value, key) => typeof value === "object" && value !== null ? value[key] : void 0, "prop");
3198
- var TRANSPORT_FALLBACK = /^HTTP \d{3}:\s*/;
3397
+ var TRANSPORT_FALLBACK = /^HTTP \d{1,3}:\s*/;
3199
3398
  var AUTH_ERROR_KEYS = {
3200
3399
  invalidCredentials: "ui.errors.unauthorized",
3201
3400
  rateLimited: "api.errors.rateLimited",
@@ -3212,6 +3411,7 @@ var FALLBACK_COPY = {
3212
3411
  };
3213
3412
  var keyForStatus = /* @__PURE__ */ __name((status) => {
3214
3413
  if (status === void 0) return AUTH_ERROR_KEYS.generic;
3414
+ if (status === 0) return AUTH_ERROR_KEYS.networkError;
3215
3415
  if (status === 401 || status === 403) return AUTH_ERROR_KEYS.invalidCredentials;
3216
3416
  if (status === 404) return AUTH_ERROR_KEYS.serverUnavailable;
3217
3417
  if (status === 429) return AUTH_ERROR_KEYS.rateLimited;
@@ -3256,9 +3456,9 @@ var logAuthFailure = /* @__PURE__ */ __name((log, message, error) => {
3256
3456
 
3257
3457
  // src/auth/utils/oauthErrors.ts
3258
3458
  var SERVER_CODES = new Set(Object.values(OAuthErrorCodeEnum));
3259
- 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");
3260
3460
  var readOAuthFailure = /* @__PURE__ */ __name((err, fallback) => {
3261
- const body = record(record(err)?.response);
3461
+ const body = record2(record2(err)?.response);
3262
3462
  const code = body?.error;
3263
3463
  if (typeof code === "string" && SERVER_CODES.has(code)) {
3264
3464
  const description = body?.error_description;
@@ -3537,6 +3737,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3537
3737
  setLoadingProvider(provider);
3538
3738
  setError(null);
3539
3739
  setErrorCode(null);
3740
+ authTrace("oauth.start");
3540
3741
  auth.clearSession();
3541
3742
  try {
3542
3743
  Analytics.event("auth_oauth_start" /* AUTH_OAUTH_START */, {
@@ -3584,6 +3785,7 @@ var useOAuthLogin = /* @__PURE__ */ __name((options = {}) => {
3584
3785
  if (!response.access || !response.refresh) {
3585
3786
  throw new Error("Invalid response from OAuth callback");
3586
3787
  }
3788
+ authTrace("oauth.session");
3587
3789
  auth.setSession({ access: response.access, refresh: response.refresh });
3588
3790
  Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
3589
3791
  category: "auth" /* AUTH */,
@@ -3764,6 +3966,7 @@ var useTwoFactorVerify = /* @__PURE__ */ __name((options = {}) => {
3764
3966
  setError(null);
3765
3967
  }, []);
3766
3968
  const handleSuccess = useCallback8((response) => {
3969
+ authTrace("2fa.session");
3767
3970
  auth.setSession({ access: response.access_token, refresh: response.refresh_token });
3768
3971
  if (response.warning) {
3769
3972
  setWarning(response.warning);
@@ -4415,6 +4618,17 @@ function purgeLegacyTokenStorage() {
4415
4618
  }
4416
4619
  __name(purgeLegacyTokenStorage, "purgeLegacyTokenStorage");
4417
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
+
4418
4632
  // src/auth/context/AccountsContext.tsx
4419
4633
  import {
4420
4634
  createContext,
@@ -4870,6 +5084,7 @@ function AccountsProvider({ children }) {
4870
5084
  return;
4871
5085
  }
4872
5086
  if (result.access && result.refresh) {
5087
+ authTrace("accounts.otp.session");
4873
5088
  auth.setSession({ access: result.access, refresh: result.refresh });
4874
5089
  try {
4875
5090
  await refreshProfile({ callerId, force: true });
@@ -4895,6 +5110,7 @@ function AccountsProvider({ children }) {
4895
5110
  authLogger.warn("Refresh-token blacklist failed (logged out locally anyway)");
4896
5111
  });
4897
5112
  }
5113
+ authTrace("accounts.logout");
4898
5114
  auth.clearSession();
4899
5115
  setProfile(void 0);
4900
5116
  setProfileError(null);
@@ -4929,11 +5145,11 @@ function AccountsProvider({ children }) {
4929
5145
  }
4930
5146
  __name(AccountsProvider, "AccountsProvider");
4931
5147
  function useAccountsContext() {
4932
- const context = useContext(AccountsContext);
4933
- if (!context) {
5148
+ const context2 = useContext(AccountsContext);
5149
+ if (!context2) {
4934
5150
  throw new Error("useAccountsContext must be used within AccountsProvider");
4935
5151
  }
4936
- return context;
5152
+ return context2;
4937
5153
  }
4938
5154
  __name(useAccountsContext, "useAccountsContext");
4939
5155
 
@@ -4989,9 +5205,11 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4989
5205
  const hasTokens = Boolean(auth.getToken() || auth.getRefreshToken());
4990
5206
  if (hasTokens && !auth.isAuthenticated()) {
4991
5207
  authLogger.warn("Dead session in storage \u2014 clearing");
5208
+ authTrace("bootstrap.purge", { why: "dead-tokens" });
4992
5209
  auth.clearSession();
4993
5210
  clearProfileCache();
4994
5211
  } else if (!hasTokens && !auth.isAuthenticated()) {
5212
+ authTrace("bootstrap.purge", { why: "no-session" });
4995
5213
  clearProfileCache();
4996
5214
  purgeLegacyTokenStorage();
4997
5215
  if (accountsRef.current.profile) accountsRef.current.logout();
@@ -5004,9 +5222,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5004
5222
  return;
5005
5223
  }
5006
5224
  profileAttemptedRef.current = true;
5225
+ authTrace("bootstrap.profile.start");
5007
5226
  let cancelled = false;
5008
5227
  accountsRef.current.refreshProfile({ callerId: "AuthProvider:bootstrap" }).catch((error) => {
5009
5228
  authLogger.warn("Profile load failed (session kept):", error);
5229
+ authTrace("bootstrap.profile.failed", {
5230
+ status: error instanceof APIError ? error.statusCode : 0
5231
+ });
5010
5232
  }).finally(() => {
5011
5233
  if (!cancelled) setProfileSettled(true);
5012
5234
  });
@@ -5015,11 +5237,47 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5015
5237
  };
5016
5238
  }, [isAuthenticated, hasProfile]);
5017
5239
  const isLoading = !profileSettled;
5018
- 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]);
5019
5278
  useEffect7(() => {
5020
5279
  const unsubscribe = auth.onSessionExpired(() => {
5021
- if (expiredHandledRef.current) return;
5022
- expiredHandledRef.current = true;
5280
+ authTrace("expired.handler", { path: pathname ?? "" });
5023
5281
  authLogger.warn("Session expired (terminal 401) \u2014 redirecting to login");
5024
5282
  clearProfileCache();
5025
5283
  Analytics.event("auth_session_expired" /* AUTH_SESSION_EXPIRED */, {
@@ -5028,12 +5286,13 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5028
5286
  router.hardReplace(routesRef.current.defaultAuthCallback);
5029
5287
  });
5030
5288
  return unsubscribe;
5031
- }, [router]);
5289
+ }, [router, pathname]);
5032
5290
  useEffect7(() => {
5033
5291
  if (!isAuthenticated || pendingManualNavRef.current) return;
5034
5292
  if (pathname === routes.auth && !queryParams.get("flow")) {
5035
5293
  const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
5036
5294
  if (saved) redirectManager.clearRedirect();
5295
+ authTrace("leave-auth", { to: saved || routes.defaultCallback, saved: Boolean(saved) });
5037
5296
  router.push(saved || routes.defaultCallback);
5038
5297
  }
5039
5298
  }, [isAuthenticated, pathname, queryParams, routes, router, redirectManager]);
@@ -5059,6 +5318,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5059
5318
  }, [loadCurrentProfile, router]);
5060
5319
  const requestOTP = useCallback13(
5061
5320
  async (identifier, sourceUrl, consent) => {
5321
+ authTrace("otp.request");
5062
5322
  auth.clearSession();
5063
5323
  try {
5064
5324
  const body = {
@@ -5103,6 +5363,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5103
5363
  async (identifier, otpCode, sourceUrl, redirectUrl, skipRedirect) => {
5104
5364
  try {
5105
5365
  pendingManualNavRef.current = Boolean(skipRedirect);
5366
+ authTrace("otp.verify", { skipRedirect: Boolean(skipRedirect) });
5106
5367
  const result = await accountsRef.current.verifyOTP({
5107
5368
  identifier,
5108
5369
  otp: otpCode,
@@ -5164,6 +5425,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5164
5425
  const { redirectUrl, skipRedirect } = options || {};
5165
5426
  try {
5166
5427
  pendingManualNavRef.current = Boolean(skipRedirect);
5428
+ authTrace("password.login", { skipRedirect: Boolean(skipRedirect) });
5167
5429
  const result = await accountsRef.current.loginWithPassword({
5168
5430
  email,
5169
5431
  password
@@ -5238,6 +5500,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5238
5500
  return { success: false, message: "Error refreshing token" };
5239
5501
  }, []);
5240
5502
  const logout = useCallback13(() => {
5503
+ authTrace("logout");
5241
5504
  Analytics.event("auth_logout" /* AUTH_LOGOUT */, {
5242
5505
  category: "auth" /* AUTH */
5243
5506
  });
@@ -5264,6 +5527,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5264
5527
  () => ({
5265
5528
  user,
5266
5529
  isLoading,
5530
+ status,
5267
5531
  // Reactive: flips via the store snapshot (token writes, refreshes,
5268
5532
  // expiry timer, cross-tab/cross-store sync) — no ticks, no polling.
5269
5533
  isAuthenticated,
@@ -5291,6 +5555,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
5291
5555
  [
5292
5556
  user,
5293
5557
  isLoading,
5558
+ status,
5294
5559
  isAuthenticated,
5295
5560
  isAdminUser,
5296
5561
  routes,
@@ -5320,6 +5585,7 @@ var AuthProvider = memo(AuthProviderRaw);
5320
5585
  var defaultAuthState = {
5321
5586
  user: null,
5322
5587
  isLoading: false,
5588
+ status: "anonymous",
5323
5589
  isAuthenticated: false,
5324
5590
  isAdminUser: false,
5325
5591
  routes: resolveAuthRoutes(),
@@ -5372,14 +5638,14 @@ var defaultAuthState = {
5372
5638
  }, "uploadAvatar")
5373
5639
  };
5374
5640
  var useAuth = /* @__PURE__ */ __name(() => {
5375
- const context = useContext2(AuthContext);
5376
- if (context === void 0) {
5641
+ const context2 = useContext2(AuthContext);
5642
+ if (context2 === void 0) {
5377
5643
  if (isBrowser2 && isDev) {
5378
5644
  authLogger.debug("useAuth called outside AuthProvider, returning default state");
5379
5645
  }
5380
5646
  return defaultAuthState;
5381
5647
  }
5382
- return context;
5648
+ return context2;
5383
5649
  }, "useAuth");
5384
5650
  var AuthContext_default = AuthContext;
5385
5651
 
@@ -5422,17 +5688,21 @@ export {
5422
5688
  AuthProvider,
5423
5689
  OAUTH_PROVIDER_IDS,
5424
5690
  authLogger,
5691
+ authTrace,
5425
5692
  beginTwoFactorChallenge,
5693
+ clearAuthTrace,
5426
5694
  clearOAuthAttempt,
5427
5695
  clearProfileCache,
5428
5696
  consumeSavedRedirect,
5429
5697
  decodeBase64,
5698
+ dumpAuthTrace,
5430
5699
  encodeBase64,
5431
5700
  endTwoFactorChallenge,
5432
5701
  formatAuthError,
5433
5702
  getCacheMetadata,
5434
5703
  getCachedProfile,
5435
5704
  hasValidCache,
5705
+ installAuthTrace,
5436
5706
  isAllowedAuthPath,
5437
5707
  isForbidden,
5438
5708
  isOAuthProviderId,
@@ -5440,8 +5710,10 @@ export {
5440
5710
  logger,
5441
5711
  normalizePath,
5442
5712
  peekSavedRedirect,
5713
+ readAuthTrace,
5443
5714
  readOAuthFailure,
5444
5715
  resolveAuthError,
5716
+ resolveAuthStatus,
5445
5717
  resolveGuardIsAuthenticated,
5446
5718
  resolveGuardIsLoading,
5447
5719
  setAnalyticsSink,