@spidy092/auth-client 3.1.3 → 3.1.5

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/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  // token.js
2
2
  import { jwtDecode } from "jwt-decode";
3
3
  var accessToken = null;
4
+ var idToken = null;
4
5
  var listeners = /* @__PURE__ */ new Set();
5
6
  var REFRESH_COOKIE = "account_refresh_token";
7
+ var ID_TOKEN_KEY = "auth_id_token";
6
8
  var COOKIE_MAX_AGE = 7 * 24 * 60 * 60;
7
9
  function secureAttribute() {
8
10
  var _a;
@@ -81,12 +83,14 @@ function getToken() {
81
83
  function clearToken() {
82
84
  if (!accessToken) {
83
85
  writeAccessToken(null);
86
+ clearIdToken();
84
87
  clearRefreshToken();
85
88
  return;
86
89
  }
87
90
  const previousToken = accessToken;
88
91
  accessToken = null;
89
92
  writeAccessToken(null);
93
+ clearIdToken();
90
94
  clearRefreshToken();
91
95
  listeners.forEach((listener) => {
92
96
  try {
@@ -96,6 +100,30 @@ function clearToken() {
96
100
  }
97
101
  });
98
102
  }
103
+ function setIdToken(token) {
104
+ idToken = token || null;
105
+ try {
106
+ if (idToken) sessionStorage.setItem(ID_TOKEN_KEY, idToken);
107
+ else sessionStorage.removeItem(ID_TOKEN_KEY);
108
+ } catch {
109
+ }
110
+ }
111
+ function getIdToken() {
112
+ if (idToken) return idToken;
113
+ try {
114
+ idToken = sessionStorage.getItem(ID_TOKEN_KEY) || null;
115
+ } catch {
116
+ idToken = null;
117
+ }
118
+ return idToken;
119
+ }
120
+ function clearIdToken() {
121
+ idToken = null;
122
+ try {
123
+ sessionStorage.removeItem(ID_TOKEN_KEY);
124
+ } catch {
125
+ }
126
+ }
99
127
  var REFRESH_TOKEN_KEY = "auth_refresh_token";
100
128
  var _persistRefreshToken = false;
101
129
  function enableRefreshTokenPersistence(enabled) {
@@ -340,6 +368,28 @@ function clearLoginLock() {
340
368
  } catch {
341
369
  }
342
370
  }
371
+ async function reportClientDiagnostic(safe) {
372
+ if (typeof fetch !== "function" || typeof getToken !== "function") return;
373
+ if (safe.outcome === "SUCCESS" && !safe.event.startsWith("PROFILE_") && !safe.event.startsWith("SESSION_")) return;
374
+ const { authBaseUrl } = getConfig();
375
+ const token = getToken();
376
+ if (!authBaseUrl || !token) return;
377
+ try {
378
+ await fetch(`${authBaseUrl.replace(/\/+$/, "")}/client-telemetry`, {
379
+ method: "POST",
380
+ credentials: "include",
381
+ keepalive: true,
382
+ headers: {
383
+ "Content-Type": "application/json",
384
+ Authorization: `Bearer ${token}`,
385
+ "X-Correlation-ID": safe.correlationId,
386
+ "X-Request-ID": safe.correlationId
387
+ },
388
+ body: JSON.stringify(safe)
389
+ });
390
+ } catch {
391
+ }
392
+ }
343
393
  async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
344
394
  const context = getDiagnosticContext();
345
395
  const safe = {
@@ -356,6 +406,7 @@ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
356
406
  };
357
407
  const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
358
408
  console[method]("[auth-client]", safe);
409
+ void reportClientDiagnostic(safe);
359
410
  return safe;
360
411
  }
361
412
 
@@ -418,6 +469,7 @@ async function logout(options = {}) {
418
469
  const { clientKey, authBaseUrl, accountUiUrl } = getConfig();
419
470
  const scope = options.scope === "client" ? "client" : "sso";
420
471
  const token = getToken();
472
+ const idToken2 = getIdToken();
421
473
  const refreshToken2 = getRefreshToken();
422
474
  console.log("\u{1F6AA} Smart Logout initiated", {
423
475
  mode: isRouterMode() ? "ROUTER" : "CLIENT",
@@ -426,6 +478,7 @@ async function logout(options = {}) {
426
478
  });
427
479
  emitAuthDiagnostic("LOGOUT_INITIATED", "PENDING", "NONE", { clientKey });
428
480
  clearToken();
481
+ clearIdToken();
429
482
  clearRefreshToken();
430
483
  sessionStorage.removeItem("originalApp");
431
484
  sessionStorage.removeItem("returnUrl");
@@ -438,7 +491,7 @@ async function logout(options = {}) {
438
491
  "Authorization": token ? `Bearer ${token}` : "",
439
492
  "Content-Type": "application/json"
440
493
  },
441
- body: JSON.stringify({ refreshToken: refreshToken2, scope })
494
+ body: JSON.stringify({ refreshToken: refreshToken2, idToken: idToken2, scope })
442
495
  });
443
496
  if (!response.ok) {
444
497
  throw new Error(`Logout failed: ${response.status}`);
@@ -472,9 +525,11 @@ async function logout(options = {}) {
472
525
  function handleCallback() {
473
526
  const params = new URLSearchParams(window.location.search);
474
527
  const accessToken2 = params.get("access_token");
528
+ const idToken2 = params.get("id_token");
475
529
  const error = params.get("error");
476
530
  console.log("\u{1F504} Callback handling:", {
477
531
  hasAccessToken: !!accessToken2,
532
+ hasIdToken: !!idToken2,
478
533
  error
479
534
  });
480
535
  if (callbackProcessed) {
@@ -502,6 +557,7 @@ function handleCallback() {
502
557
  }
503
558
  if (accessToken2) {
504
559
  setToken(accessToken2);
560
+ if (idToken2) setIdToken(idToken2);
505
561
  const refreshTokenInUrl = params.get("refresh_token");
506
562
  if (refreshTokenInUrl) {
507
563
  emitAuthDiagnostic("CALLBACK_REFRESH_TOKEN_IGNORED", "WARNING", "REFRESH_TOKEN_IN_URL", {
@@ -510,6 +566,7 @@ function handleCallback() {
510
566
  }
511
567
  const url = new URL(window.location);
512
568
  url.searchParams.delete("access_token");
569
+ url.searchParams.delete("id_token");
513
570
  url.searchParams.delete("refresh_token");
514
571
  url.searchParams.delete("state");
515
572
  url.searchParams.delete("error");
@@ -1185,23 +1242,72 @@ var preferences = {
1185
1242
  // react/AuthProvider.jsx
1186
1243
  import React, { createContext, useState, useEffect, useRef } from "react";
1187
1244
  var AuthContext = createContext();
1188
- function AuthProvider({ children, onSessionExpired }) {
1245
+ function AuthProvider({ children, onSessionExpired, manageSessionSecurity = true }) {
1189
1246
  const [token, setTokenState] = useState(getToken());
1190
1247
  const [user, setUser] = useState(null);
1191
1248
  const [loading, setLoading] = useState(!!token);
1192
1249
  const [sessionValid, setSessionValid] = useState(true);
1193
1250
  const sessionSecurityRef = useRef(null);
1251
+ const onSessionExpiredRef = useRef(onSessionExpired);
1252
+ const recoveryInFlightRef = useRef(null);
1253
+ const profileRecoveryTokensRef = useRef(/* @__PURE__ */ new Set());
1254
+ useEffect(() => {
1255
+ onSessionExpiredRef.current = onSessionExpired;
1256
+ }, [onSessionExpired]);
1257
+ useEffect(() => {
1258
+ const unsubscribe = addTokenListener((nextToken, previousToken) => {
1259
+ if (nextToken === previousToken) return;
1260
+ setTokenState(nextToken);
1261
+ if (nextToken) {
1262
+ setSessionValid(true);
1263
+ setLoading(true);
1264
+ } else {
1265
+ setSessionValid(false);
1266
+ setUser(null);
1267
+ setLoading(false);
1268
+ }
1269
+ });
1270
+ return unsubscribe;
1271
+ }, []);
1272
+ const invalidateLocalSession = () => {
1273
+ clearToken();
1274
+ setTokenState(null);
1275
+ setUser(null);
1276
+ setSessionValid(false);
1277
+ setLoading(false);
1278
+ };
1194
1279
  const handleSessionInvalid = (reason) => {
1195
1280
  console.log("\u{1F6A8} AuthProvider: Session invalidated -", reason);
1196
- setSessionValid(false);
1197
- setUser(null);
1198
- setTokenState(null);
1199
- if (onSessionExpired && typeof onSessionExpired === "function") {
1200
- onSessionExpired(reason);
1201
- }
1281
+ if (recoveryInFlightRef.current) return recoveryInFlightRef.current;
1282
+ const recover = async () => {
1283
+ const callback = onSessionExpiredRef.current;
1284
+ if (typeof callback === "function") {
1285
+ try {
1286
+ await callback(reason);
1287
+ const recoveredToken = getToken();
1288
+ if (recoveredToken) {
1289
+ setTokenState(recoveredToken);
1290
+ setSessionValid(true);
1291
+ setLoading(true);
1292
+ return true;
1293
+ }
1294
+ } catch (error) {
1295
+ await emitAuthDiagnostic("SESSION_RECOVERY_FAILED", "FAILURE", reason, {
1296
+ status: (error == null ? void 0 : error.status) || null,
1297
+ errorCode: (error == null ? void 0 : error.code) || null
1298
+ });
1299
+ }
1300
+ }
1301
+ invalidateLocalSession();
1302
+ return false;
1303
+ };
1304
+ recoveryInFlightRef.current = recover().finally(() => {
1305
+ recoveryInFlightRef.current = null;
1306
+ });
1307
+ return recoveryInFlightRef.current;
1202
1308
  };
1203
1309
  useEffect(() => {
1204
- if (token && !sessionSecurityRef.current) {
1310
+ if (manageSessionSecurity && token && !sessionSecurityRef.current) {
1205
1311
  console.log("\u{1F510} AuthProvider: Starting session security");
1206
1312
  const unsubscribe = onSessionInvalid(handleSessionInvalid);
1207
1313
  sessionSecurityRef.current = startSessionSecurity(handleSessionInvalid);
@@ -1213,11 +1319,11 @@ function AuthProvider({ children, onSessionExpired }) {
1213
1319
  }
1214
1320
  };
1215
1321
  }
1216
- if (!token && sessionSecurityRef.current) {
1322
+ if ((!manageSessionSecurity || !token) && sessionSecurityRef.current) {
1217
1323
  sessionSecurityRef.current.stopAll();
1218
1324
  sessionSecurityRef.current = null;
1219
1325
  }
1220
- }, [token]);
1326
+ }, [manageSessionSecurity, token]);
1221
1327
  useEffect(() => {
1222
1328
  console.log("\u{1F50D} AuthProvider useEffect triggered:", {
1223
1329
  hasToken: !!token,
@@ -1238,26 +1344,79 @@ function AuthProvider({ children, onSessionExpired }) {
1238
1344
  authBaseUrl,
1239
1345
  tokenPreview: token.slice(0, 50) + "..."
1240
1346
  });
1241
- fetch(`${authBaseUrl}/account/profile`, {
1242
- headers: { Authorization: `Bearer ${token}` },
1243
- credentials: "include"
1244
- }).then((res) => {
1245
- console.log("\u{1F4E5} Profile response status:", res.status);
1246
- if (!res.ok) throw new Error("Failed to fetch user");
1247
- return res.json();
1248
- }).then((responseBody) => {
1249
- const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
1250
- console.log("\u2705 Profile fetched successfully:", userData.email);
1251
- setUser(userData);
1252
- setSessionValid(true);
1253
- setLoading(false);
1254
- }).catch((err) => {
1255
- console.error("\u274C Fetch user error:", err);
1256
- clearToken();
1257
- setTokenState(null);
1258
- setUser(null);
1259
- setLoading(false);
1260
- });
1347
+ let cancelled = false;
1348
+ const fetchProfile = async (accessToken2) => {
1349
+ const response = await fetch(`${authBaseUrl}/account/profile`, {
1350
+ headers: { Authorization: `Bearer ${accessToken2}` },
1351
+ credentials: "include"
1352
+ });
1353
+ console.log("\u{1F4E5} Profile response status:", response.status);
1354
+ if (!response.ok) {
1355
+ const error = new Error(`Profile request failed: ${response.status}`);
1356
+ error.status = response.status;
1357
+ throw error;
1358
+ }
1359
+ return response.json();
1360
+ };
1361
+ const loadProfile = async () => {
1362
+ try {
1363
+ const responseBody = await fetchProfile(token);
1364
+ if (cancelled) return;
1365
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
1366
+ console.log("\u2705 Profile fetched successfully:", userData.email);
1367
+ setUser(userData);
1368
+ setSessionValid(true);
1369
+ setLoading(false);
1370
+ } catch (error) {
1371
+ if (cancelled) return;
1372
+ const status = Number((error == null ? void 0 : error.status) || 0);
1373
+ const isUnauthorized = status === 401;
1374
+ const callback = onSessionExpiredRef.current;
1375
+ await emitAuthDiagnostic(
1376
+ "PROFILE_REQUEST_FAILED",
1377
+ isUnauthorized ? "FAILURE" : "WARNING",
1378
+ isUnauthorized ? "PROFILE_UNAUTHORIZED" : status ? `PROFILE_HTTP_${status}` : "PROFILE_NETWORK_ERROR",
1379
+ { status, errorName: error == null ? void 0 : error.name, errorCode: error == null ? void 0 : error.code }
1380
+ );
1381
+ if (!isUnauthorized) {
1382
+ console.warn("\u26A0\uFE0F Profile unavailable; retaining the current auth session", error);
1383
+ setLoading(false);
1384
+ return;
1385
+ }
1386
+ if (typeof callback === "function" && !profileRecoveryTokensRef.current.has(token)) {
1387
+ profileRecoveryTokensRef.current.add(token);
1388
+ try {
1389
+ await callback("profile_unauthorized");
1390
+ const recoveredToken = getToken();
1391
+ if (recoveredToken && recoveredToken !== token) {
1392
+ setTokenState(recoveredToken);
1393
+ setSessionValid(true);
1394
+ setLoading(true);
1395
+ return;
1396
+ }
1397
+ if (recoveredToken) {
1398
+ const responseBody = await fetchProfile(recoveredToken);
1399
+ if (cancelled) return;
1400
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
1401
+ setUser(userData);
1402
+ setSessionValid(true);
1403
+ setLoading(false);
1404
+ return;
1405
+ }
1406
+ } catch (recoveryError) {
1407
+ await emitAuthDiagnostic("PROFILE_RECOVERY_FAILED", "FAILURE", "PROFILE_REFRESH_FAILED", {
1408
+ status: (recoveryError == null ? void 0 : recoveryError.status) || null,
1409
+ errorCode: (recoveryError == null ? void 0 : recoveryError.code) || null
1410
+ });
1411
+ }
1412
+ }
1413
+ invalidateLocalSession();
1414
+ }
1415
+ };
1416
+ void loadProfile();
1417
+ return () => {
1418
+ cancelled = true;
1419
+ };
1261
1420
  }, [token]);
1262
1421
  const login2 = (clientKey, redirectUri, state) => {
1263
1422
  login(clientKey, redirectUri, state);
@@ -1286,6 +1445,7 @@ function AuthProvider({ children, onSessionExpired }) {
1286
1445
  setToken(newToken);
1287
1446
  setTokenState(newToken);
1288
1447
  setSessionValid(true);
1448
+ if (newToken) setLoading(true);
1289
1449
  },
1290
1450
  clearToken: () => {
1291
1451
  stopSessionSecurity();
@@ -1470,6 +1630,9 @@ var auth = {
1470
1630
  getToken,
1471
1631
  setToken,
1472
1632
  clearToken,
1633
+ setIdToken,
1634
+ getIdToken,
1635
+ clearIdToken,
1473
1636
  setRefreshToken,
1474
1637
  // ✅ Refresh token for HTTP dev
1475
1638
  getRefreshToken,