@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.
@@ -38,8 +38,10 @@ var import_react = __toESM(require("react"), 1);
38
38
  // token.js
39
39
  var import_jwt_decode = require("jwt-decode");
40
40
  var accessToken = null;
41
+ var idToken = null;
41
42
  var listeners = /* @__PURE__ */ new Set();
42
43
  var REFRESH_COOKIE = "account_refresh_token";
44
+ var ID_TOKEN_KEY = "auth_id_token";
43
45
  var COOKIE_MAX_AGE = 7 * 24 * 60 * 60;
44
46
  function secureAttribute() {
45
47
  var _a;
@@ -108,12 +110,14 @@ function getToken() {
108
110
  function clearToken() {
109
111
  if (!accessToken) {
110
112
  writeAccessToken(null);
113
+ clearIdToken();
111
114
  clearRefreshToken();
112
115
  return;
113
116
  }
114
117
  const previousToken = accessToken;
115
118
  accessToken = null;
116
119
  writeAccessToken(null);
120
+ clearIdToken();
117
121
  clearRefreshToken();
118
122
  listeners.forEach((listener) => {
119
123
  try {
@@ -123,6 +127,22 @@ function clearToken() {
123
127
  }
124
128
  });
125
129
  }
130
+ function getIdToken() {
131
+ if (idToken) return idToken;
132
+ try {
133
+ idToken = sessionStorage.getItem(ID_TOKEN_KEY) || null;
134
+ } catch {
135
+ idToken = null;
136
+ }
137
+ return idToken;
138
+ }
139
+ function clearIdToken() {
140
+ idToken = null;
141
+ try {
142
+ sessionStorage.removeItem(ID_TOKEN_KEY);
143
+ } catch {
144
+ }
145
+ }
126
146
  var REFRESH_TOKEN_KEY = "auth_refresh_token";
127
147
  var _persistRefreshToken = false;
128
148
  function shouldUseLocalStorage() {
@@ -177,6 +197,15 @@ function clearRefreshToken() {
177
197
  } catch (err) {
178
198
  }
179
199
  }
200
+ function addTokenListener(listener) {
201
+ if (typeof listener !== "function") {
202
+ throw new Error("Token listener must be a function");
203
+ }
204
+ listeners.add(listener);
205
+ return () => {
206
+ listeners.delete(listener);
207
+ };
208
+ }
180
209
  if (typeof window !== "undefined" && window.addEventListener) {
181
210
  window.addEventListener("storage", (event) => {
182
211
  if (event.key !== "authToken") return;
@@ -290,6 +319,28 @@ function acquireLoginLock(clientKey, redirectUri, ttlMs = 5e3) {
290
319
  }
291
320
  return true;
292
321
  }
322
+ async function reportClientDiagnostic(safe) {
323
+ if (typeof fetch !== "function" || typeof getToken !== "function") return;
324
+ if (safe.outcome === "SUCCESS" && !safe.event.startsWith("PROFILE_") && !safe.event.startsWith("SESSION_")) return;
325
+ const { authBaseUrl } = getConfig();
326
+ const token = getToken();
327
+ if (!authBaseUrl || !token) return;
328
+ try {
329
+ await fetch(`${authBaseUrl.replace(/\/+$/, "")}/client-telemetry`, {
330
+ method: "POST",
331
+ credentials: "include",
332
+ keepalive: true,
333
+ headers: {
334
+ "Content-Type": "application/json",
335
+ Authorization: `Bearer ${token}`,
336
+ "X-Correlation-ID": safe.correlationId,
337
+ "X-Request-ID": safe.correlationId
338
+ },
339
+ body: JSON.stringify(safe)
340
+ });
341
+ } catch {
342
+ }
343
+ }
293
344
  async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
294
345
  const context = getDiagnosticContext();
295
346
  const safe = {
@@ -306,6 +357,7 @@ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
306
357
  };
307
358
  const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
308
359
  console[method]("[auth-client]", safe);
360
+ void reportClientDiagnostic(safe);
309
361
  return safe;
310
362
  }
311
363
 
@@ -368,6 +420,7 @@ async function logout(options = {}) {
368
420
  const { clientKey, authBaseUrl, accountUiUrl } = getConfig();
369
421
  const scope = options.scope === "client" ? "client" : "sso";
370
422
  const token = getToken();
423
+ const idToken2 = getIdToken();
371
424
  const refreshToken2 = getRefreshToken();
372
425
  console.log("\u{1F6AA} Smart Logout initiated", {
373
426
  mode: isRouterMode() ? "ROUTER" : "CLIENT",
@@ -376,6 +429,7 @@ async function logout(options = {}) {
376
429
  });
377
430
  emitAuthDiagnostic("LOGOUT_INITIATED", "PENDING", "NONE", { clientKey });
378
431
  clearToken();
432
+ clearIdToken();
379
433
  clearRefreshToken();
380
434
  sessionStorage.removeItem("originalApp");
381
435
  sessionStorage.removeItem("returnUrl");
@@ -388,7 +442,7 @@ async function logout(options = {}) {
388
442
  "Authorization": token ? `Bearer ${token}` : "",
389
443
  "Content-Type": "application/json"
390
444
  },
391
- body: JSON.stringify({ refreshToken: refreshToken2, scope })
445
+ body: JSON.stringify({ refreshToken: refreshToken2, idToken: idToken2, scope })
392
446
  });
393
447
  if (!response.ok) {
394
448
  throw new Error(`Logout failed: ${response.status}`);
@@ -773,23 +827,72 @@ function stopSessionSecurity() {
773
827
 
774
828
  // react/AuthProvider.jsx
775
829
  var AuthContext = (0, import_react.createContext)();
776
- function AuthProvider({ children, onSessionExpired }) {
830
+ function AuthProvider({ children, onSessionExpired, manageSessionSecurity = true }) {
777
831
  const [token, setTokenState] = (0, import_react.useState)(getToken());
778
832
  const [user, setUser] = (0, import_react.useState)(null);
779
833
  const [loading, setLoading] = (0, import_react.useState)(!!token);
780
834
  const [sessionValid, setSessionValid] = (0, import_react.useState)(true);
781
835
  const sessionSecurityRef = (0, import_react.useRef)(null);
836
+ const onSessionExpiredRef = (0, import_react.useRef)(onSessionExpired);
837
+ const recoveryInFlightRef = (0, import_react.useRef)(null);
838
+ const profileRecoveryTokensRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
839
+ (0, import_react.useEffect)(() => {
840
+ onSessionExpiredRef.current = onSessionExpired;
841
+ }, [onSessionExpired]);
842
+ (0, import_react.useEffect)(() => {
843
+ const unsubscribe = addTokenListener((nextToken, previousToken) => {
844
+ if (nextToken === previousToken) return;
845
+ setTokenState(nextToken);
846
+ if (nextToken) {
847
+ setSessionValid(true);
848
+ setLoading(true);
849
+ } else {
850
+ setSessionValid(false);
851
+ setUser(null);
852
+ setLoading(false);
853
+ }
854
+ });
855
+ return unsubscribe;
856
+ }, []);
857
+ const invalidateLocalSession = () => {
858
+ clearToken();
859
+ setTokenState(null);
860
+ setUser(null);
861
+ setSessionValid(false);
862
+ setLoading(false);
863
+ };
782
864
  const handleSessionInvalid = (reason) => {
783
865
  console.log("\u{1F6A8} AuthProvider: Session invalidated -", reason);
784
- setSessionValid(false);
785
- setUser(null);
786
- setTokenState(null);
787
- if (onSessionExpired && typeof onSessionExpired === "function") {
788
- onSessionExpired(reason);
789
- }
866
+ if (recoveryInFlightRef.current) return recoveryInFlightRef.current;
867
+ const recover = async () => {
868
+ const callback = onSessionExpiredRef.current;
869
+ if (typeof callback === "function") {
870
+ try {
871
+ await callback(reason);
872
+ const recoveredToken = getToken();
873
+ if (recoveredToken) {
874
+ setTokenState(recoveredToken);
875
+ setSessionValid(true);
876
+ setLoading(true);
877
+ return true;
878
+ }
879
+ } catch (error) {
880
+ await emitAuthDiagnostic("SESSION_RECOVERY_FAILED", "FAILURE", reason, {
881
+ status: (error == null ? void 0 : error.status) || null,
882
+ errorCode: (error == null ? void 0 : error.code) || null
883
+ });
884
+ }
885
+ }
886
+ invalidateLocalSession();
887
+ return false;
888
+ };
889
+ recoveryInFlightRef.current = recover().finally(() => {
890
+ recoveryInFlightRef.current = null;
891
+ });
892
+ return recoveryInFlightRef.current;
790
893
  };
791
894
  (0, import_react.useEffect)(() => {
792
- if (token && !sessionSecurityRef.current) {
895
+ if (manageSessionSecurity && token && !sessionSecurityRef.current) {
793
896
  console.log("\u{1F510} AuthProvider: Starting session security");
794
897
  const unsubscribe = onSessionInvalid(handleSessionInvalid);
795
898
  sessionSecurityRef.current = startSessionSecurity(handleSessionInvalid);
@@ -801,11 +904,11 @@ function AuthProvider({ children, onSessionExpired }) {
801
904
  }
802
905
  };
803
906
  }
804
- if (!token && sessionSecurityRef.current) {
907
+ if ((!manageSessionSecurity || !token) && sessionSecurityRef.current) {
805
908
  sessionSecurityRef.current.stopAll();
806
909
  sessionSecurityRef.current = null;
807
910
  }
808
- }, [token]);
911
+ }, [manageSessionSecurity, token]);
809
912
  (0, import_react.useEffect)(() => {
810
913
  console.log("\u{1F50D} AuthProvider useEffect triggered:", {
811
914
  hasToken: !!token,
@@ -826,26 +929,79 @@ function AuthProvider({ children, onSessionExpired }) {
826
929
  authBaseUrl,
827
930
  tokenPreview: token.slice(0, 50) + "..."
828
931
  });
829
- fetch(`${authBaseUrl}/account/profile`, {
830
- headers: { Authorization: `Bearer ${token}` },
831
- credentials: "include"
832
- }).then((res) => {
833
- console.log("\u{1F4E5} Profile response status:", res.status);
834
- if (!res.ok) throw new Error("Failed to fetch user");
835
- return res.json();
836
- }).then((responseBody) => {
837
- const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
838
- console.log("\u2705 Profile fetched successfully:", userData.email);
839
- setUser(userData);
840
- setSessionValid(true);
841
- setLoading(false);
842
- }).catch((err) => {
843
- console.error("\u274C Fetch user error:", err);
844
- clearToken();
845
- setTokenState(null);
846
- setUser(null);
847
- setLoading(false);
848
- });
932
+ let cancelled = false;
933
+ const fetchProfile = async (accessToken2) => {
934
+ const response = await fetch(`${authBaseUrl}/account/profile`, {
935
+ headers: { Authorization: `Bearer ${accessToken2}` },
936
+ credentials: "include"
937
+ });
938
+ console.log("\u{1F4E5} Profile response status:", response.status);
939
+ if (!response.ok) {
940
+ const error = new Error(`Profile request failed: ${response.status}`);
941
+ error.status = response.status;
942
+ throw error;
943
+ }
944
+ return response.json();
945
+ };
946
+ const loadProfile = async () => {
947
+ try {
948
+ const responseBody = await fetchProfile(token);
949
+ if (cancelled) return;
950
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
951
+ console.log("\u2705 Profile fetched successfully:", userData.email);
952
+ setUser(userData);
953
+ setSessionValid(true);
954
+ setLoading(false);
955
+ } catch (error) {
956
+ if (cancelled) return;
957
+ const status = Number((error == null ? void 0 : error.status) || 0);
958
+ const isUnauthorized = status === 401;
959
+ const callback = onSessionExpiredRef.current;
960
+ await emitAuthDiagnostic(
961
+ "PROFILE_REQUEST_FAILED",
962
+ isUnauthorized ? "FAILURE" : "WARNING",
963
+ isUnauthorized ? "PROFILE_UNAUTHORIZED" : status ? `PROFILE_HTTP_${status}` : "PROFILE_NETWORK_ERROR",
964
+ { status, errorName: error == null ? void 0 : error.name, errorCode: error == null ? void 0 : error.code }
965
+ );
966
+ if (!isUnauthorized) {
967
+ console.warn("\u26A0\uFE0F Profile unavailable; retaining the current auth session", error);
968
+ setLoading(false);
969
+ return;
970
+ }
971
+ if (typeof callback === "function" && !profileRecoveryTokensRef.current.has(token)) {
972
+ profileRecoveryTokensRef.current.add(token);
973
+ try {
974
+ await callback("profile_unauthorized");
975
+ const recoveredToken = getToken();
976
+ if (recoveredToken && recoveredToken !== token) {
977
+ setTokenState(recoveredToken);
978
+ setSessionValid(true);
979
+ setLoading(true);
980
+ return;
981
+ }
982
+ if (recoveredToken) {
983
+ const responseBody = await fetchProfile(recoveredToken);
984
+ if (cancelled) return;
985
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
986
+ setUser(userData);
987
+ setSessionValid(true);
988
+ setLoading(false);
989
+ return;
990
+ }
991
+ } catch (recoveryError) {
992
+ await emitAuthDiagnostic("PROFILE_RECOVERY_FAILED", "FAILURE", "PROFILE_REFRESH_FAILED", {
993
+ status: (recoveryError == null ? void 0 : recoveryError.status) || null,
994
+ errorCode: (recoveryError == null ? void 0 : recoveryError.code) || null
995
+ });
996
+ }
997
+ }
998
+ invalidateLocalSession();
999
+ }
1000
+ };
1001
+ void loadProfile();
1002
+ return () => {
1003
+ cancelled = true;
1004
+ };
849
1005
  }, [token]);
850
1006
  const login2 = (clientKey, redirectUri, state) => {
851
1007
  login(clientKey, redirectUri, state);
@@ -874,6 +1030,7 @@ function AuthProvider({ children, onSessionExpired }) {
874
1030
  setToken(newToken);
875
1031
  setTokenState(newToken);
876
1032
  setSessionValid(true);
1033
+ if (newToken) setLoading(true);
877
1034
  },
878
1035
  clearToken: () => {
879
1036
  stopSessionSecurity();