@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.
@@ -4,8 +4,10 @@ import React, { createContext, useState, useEffect, useRef } from "react";
4
4
  // token.js
5
5
  import { jwtDecode } from "jwt-decode";
6
6
  var accessToken = null;
7
+ var idToken = null;
7
8
  var listeners = /* @__PURE__ */ new Set();
8
9
  var REFRESH_COOKIE = "account_refresh_token";
10
+ var ID_TOKEN_KEY = "auth_id_token";
9
11
  var COOKIE_MAX_AGE = 7 * 24 * 60 * 60;
10
12
  function secureAttribute() {
11
13
  var _a;
@@ -74,12 +76,14 @@ function getToken() {
74
76
  function clearToken() {
75
77
  if (!accessToken) {
76
78
  writeAccessToken(null);
79
+ clearIdToken();
77
80
  clearRefreshToken();
78
81
  return;
79
82
  }
80
83
  const previousToken = accessToken;
81
84
  accessToken = null;
82
85
  writeAccessToken(null);
86
+ clearIdToken();
83
87
  clearRefreshToken();
84
88
  listeners.forEach((listener) => {
85
89
  try {
@@ -89,6 +93,22 @@ function clearToken() {
89
93
  }
90
94
  });
91
95
  }
96
+ function getIdToken() {
97
+ if (idToken) return idToken;
98
+ try {
99
+ idToken = sessionStorage.getItem(ID_TOKEN_KEY) || null;
100
+ } catch {
101
+ idToken = null;
102
+ }
103
+ return idToken;
104
+ }
105
+ function clearIdToken() {
106
+ idToken = null;
107
+ try {
108
+ sessionStorage.removeItem(ID_TOKEN_KEY);
109
+ } catch {
110
+ }
111
+ }
92
112
  var REFRESH_TOKEN_KEY = "auth_refresh_token";
93
113
  var _persistRefreshToken = false;
94
114
  function shouldUseLocalStorage() {
@@ -143,6 +163,15 @@ function clearRefreshToken() {
143
163
  } catch (err) {
144
164
  }
145
165
  }
166
+ function addTokenListener(listener) {
167
+ if (typeof listener !== "function") {
168
+ throw new Error("Token listener must be a function");
169
+ }
170
+ listeners.add(listener);
171
+ return () => {
172
+ listeners.delete(listener);
173
+ };
174
+ }
146
175
  if (typeof window !== "undefined" && window.addEventListener) {
147
176
  window.addEventListener("storage", (event) => {
148
177
  if (event.key !== "authToken") return;
@@ -256,6 +285,28 @@ function acquireLoginLock(clientKey, redirectUri, ttlMs = 5e3) {
256
285
  }
257
286
  return true;
258
287
  }
288
+ async function reportClientDiagnostic(safe) {
289
+ if (typeof fetch !== "function" || typeof getToken !== "function") return;
290
+ if (safe.outcome === "SUCCESS" && !safe.event.startsWith("PROFILE_") && !safe.event.startsWith("SESSION_")) return;
291
+ const { authBaseUrl } = getConfig();
292
+ const token = getToken();
293
+ if (!authBaseUrl || !token) return;
294
+ try {
295
+ await fetch(`${authBaseUrl.replace(/\/+$/, "")}/client-telemetry`, {
296
+ method: "POST",
297
+ credentials: "include",
298
+ keepalive: true,
299
+ headers: {
300
+ "Content-Type": "application/json",
301
+ Authorization: `Bearer ${token}`,
302
+ "X-Correlation-ID": safe.correlationId,
303
+ "X-Request-ID": safe.correlationId
304
+ },
305
+ body: JSON.stringify(safe)
306
+ });
307
+ } catch {
308
+ }
309
+ }
259
310
  async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
260
311
  const context = getDiagnosticContext();
261
312
  const safe = {
@@ -272,6 +323,7 @@ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
272
323
  };
273
324
  const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
274
325
  console[method]("[auth-client]", safe);
326
+ void reportClientDiagnostic(safe);
275
327
  return safe;
276
328
  }
277
329
 
@@ -334,6 +386,7 @@ async function logout(options = {}) {
334
386
  const { clientKey, authBaseUrl, accountUiUrl } = getConfig();
335
387
  const scope = options.scope === "client" ? "client" : "sso";
336
388
  const token = getToken();
389
+ const idToken2 = getIdToken();
337
390
  const refreshToken2 = getRefreshToken();
338
391
  console.log("\u{1F6AA} Smart Logout initiated", {
339
392
  mode: isRouterMode() ? "ROUTER" : "CLIENT",
@@ -342,6 +395,7 @@ async function logout(options = {}) {
342
395
  });
343
396
  emitAuthDiagnostic("LOGOUT_INITIATED", "PENDING", "NONE", { clientKey });
344
397
  clearToken();
398
+ clearIdToken();
345
399
  clearRefreshToken();
346
400
  sessionStorage.removeItem("originalApp");
347
401
  sessionStorage.removeItem("returnUrl");
@@ -354,7 +408,7 @@ async function logout(options = {}) {
354
408
  "Authorization": token ? `Bearer ${token}` : "",
355
409
  "Content-Type": "application/json"
356
410
  },
357
- body: JSON.stringify({ refreshToken: refreshToken2, scope })
411
+ body: JSON.stringify({ refreshToken: refreshToken2, idToken: idToken2, scope })
358
412
  });
359
413
  if (!response.ok) {
360
414
  throw new Error(`Logout failed: ${response.status}`);
@@ -739,23 +793,72 @@ function stopSessionSecurity() {
739
793
 
740
794
  // react/AuthProvider.jsx
741
795
  var AuthContext = createContext();
742
- function AuthProvider({ children, onSessionExpired }) {
796
+ function AuthProvider({ children, onSessionExpired, manageSessionSecurity = true }) {
743
797
  const [token, setTokenState] = useState(getToken());
744
798
  const [user, setUser] = useState(null);
745
799
  const [loading, setLoading] = useState(!!token);
746
800
  const [sessionValid, setSessionValid] = useState(true);
747
801
  const sessionSecurityRef = useRef(null);
802
+ const onSessionExpiredRef = useRef(onSessionExpired);
803
+ const recoveryInFlightRef = useRef(null);
804
+ const profileRecoveryTokensRef = useRef(/* @__PURE__ */ new Set());
805
+ useEffect(() => {
806
+ onSessionExpiredRef.current = onSessionExpired;
807
+ }, [onSessionExpired]);
808
+ useEffect(() => {
809
+ const unsubscribe = addTokenListener((nextToken, previousToken) => {
810
+ if (nextToken === previousToken) return;
811
+ setTokenState(nextToken);
812
+ if (nextToken) {
813
+ setSessionValid(true);
814
+ setLoading(true);
815
+ } else {
816
+ setSessionValid(false);
817
+ setUser(null);
818
+ setLoading(false);
819
+ }
820
+ });
821
+ return unsubscribe;
822
+ }, []);
823
+ const invalidateLocalSession = () => {
824
+ clearToken();
825
+ setTokenState(null);
826
+ setUser(null);
827
+ setSessionValid(false);
828
+ setLoading(false);
829
+ };
748
830
  const handleSessionInvalid = (reason) => {
749
831
  console.log("\u{1F6A8} AuthProvider: Session invalidated -", reason);
750
- setSessionValid(false);
751
- setUser(null);
752
- setTokenState(null);
753
- if (onSessionExpired && typeof onSessionExpired === "function") {
754
- onSessionExpired(reason);
755
- }
832
+ if (recoveryInFlightRef.current) return recoveryInFlightRef.current;
833
+ const recover = async () => {
834
+ const callback = onSessionExpiredRef.current;
835
+ if (typeof callback === "function") {
836
+ try {
837
+ await callback(reason);
838
+ const recoveredToken = getToken();
839
+ if (recoveredToken) {
840
+ setTokenState(recoveredToken);
841
+ setSessionValid(true);
842
+ setLoading(true);
843
+ return true;
844
+ }
845
+ } catch (error) {
846
+ await emitAuthDiagnostic("SESSION_RECOVERY_FAILED", "FAILURE", reason, {
847
+ status: (error == null ? void 0 : error.status) || null,
848
+ errorCode: (error == null ? void 0 : error.code) || null
849
+ });
850
+ }
851
+ }
852
+ invalidateLocalSession();
853
+ return false;
854
+ };
855
+ recoveryInFlightRef.current = recover().finally(() => {
856
+ recoveryInFlightRef.current = null;
857
+ });
858
+ return recoveryInFlightRef.current;
756
859
  };
757
860
  useEffect(() => {
758
- if (token && !sessionSecurityRef.current) {
861
+ if (manageSessionSecurity && token && !sessionSecurityRef.current) {
759
862
  console.log("\u{1F510} AuthProvider: Starting session security");
760
863
  const unsubscribe = onSessionInvalid(handleSessionInvalid);
761
864
  sessionSecurityRef.current = startSessionSecurity(handleSessionInvalid);
@@ -767,11 +870,11 @@ function AuthProvider({ children, onSessionExpired }) {
767
870
  }
768
871
  };
769
872
  }
770
- if (!token && sessionSecurityRef.current) {
873
+ if ((!manageSessionSecurity || !token) && sessionSecurityRef.current) {
771
874
  sessionSecurityRef.current.stopAll();
772
875
  sessionSecurityRef.current = null;
773
876
  }
774
- }, [token]);
877
+ }, [manageSessionSecurity, token]);
775
878
  useEffect(() => {
776
879
  console.log("\u{1F50D} AuthProvider useEffect triggered:", {
777
880
  hasToken: !!token,
@@ -792,26 +895,79 @@ function AuthProvider({ children, onSessionExpired }) {
792
895
  authBaseUrl,
793
896
  tokenPreview: token.slice(0, 50) + "..."
794
897
  });
795
- fetch(`${authBaseUrl}/account/profile`, {
796
- headers: { Authorization: `Bearer ${token}` },
797
- credentials: "include"
798
- }).then((res) => {
799
- console.log("\u{1F4E5} Profile response status:", res.status);
800
- if (!res.ok) throw new Error("Failed to fetch user");
801
- return res.json();
802
- }).then((responseBody) => {
803
- const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
804
- console.log("\u2705 Profile fetched successfully:", userData.email);
805
- setUser(userData);
806
- setSessionValid(true);
807
- setLoading(false);
808
- }).catch((err) => {
809
- console.error("\u274C Fetch user error:", err);
810
- clearToken();
811
- setTokenState(null);
812
- setUser(null);
813
- setLoading(false);
814
- });
898
+ let cancelled = false;
899
+ const fetchProfile = async (accessToken2) => {
900
+ const response = await fetch(`${authBaseUrl}/account/profile`, {
901
+ headers: { Authorization: `Bearer ${accessToken2}` },
902
+ credentials: "include"
903
+ });
904
+ console.log("\u{1F4E5} Profile response status:", response.status);
905
+ if (!response.ok) {
906
+ const error = new Error(`Profile request failed: ${response.status}`);
907
+ error.status = response.status;
908
+ throw error;
909
+ }
910
+ return response.json();
911
+ };
912
+ const loadProfile = async () => {
913
+ try {
914
+ const responseBody = await fetchProfile(token);
915
+ if (cancelled) return;
916
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
917
+ console.log("\u2705 Profile fetched successfully:", userData.email);
918
+ setUser(userData);
919
+ setSessionValid(true);
920
+ setLoading(false);
921
+ } catch (error) {
922
+ if (cancelled) return;
923
+ const status = Number((error == null ? void 0 : error.status) || 0);
924
+ const isUnauthorized = status === 401;
925
+ const callback = onSessionExpiredRef.current;
926
+ await emitAuthDiagnostic(
927
+ "PROFILE_REQUEST_FAILED",
928
+ isUnauthorized ? "FAILURE" : "WARNING",
929
+ isUnauthorized ? "PROFILE_UNAUTHORIZED" : status ? `PROFILE_HTTP_${status}` : "PROFILE_NETWORK_ERROR",
930
+ { status, errorName: error == null ? void 0 : error.name, errorCode: error == null ? void 0 : error.code }
931
+ );
932
+ if (!isUnauthorized) {
933
+ console.warn("\u26A0\uFE0F Profile unavailable; retaining the current auth session", error);
934
+ setLoading(false);
935
+ return;
936
+ }
937
+ if (typeof callback === "function" && !profileRecoveryTokensRef.current.has(token)) {
938
+ profileRecoveryTokensRef.current.add(token);
939
+ try {
940
+ await callback("profile_unauthorized");
941
+ const recoveredToken = getToken();
942
+ if (recoveredToken && recoveredToken !== token) {
943
+ setTokenState(recoveredToken);
944
+ setSessionValid(true);
945
+ setLoading(true);
946
+ return;
947
+ }
948
+ if (recoveredToken) {
949
+ const responseBody = await fetchProfile(recoveredToken);
950
+ if (cancelled) return;
951
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
952
+ setUser(userData);
953
+ setSessionValid(true);
954
+ setLoading(false);
955
+ return;
956
+ }
957
+ } catch (recoveryError) {
958
+ await emitAuthDiagnostic("PROFILE_RECOVERY_FAILED", "FAILURE", "PROFILE_REFRESH_FAILED", {
959
+ status: (recoveryError == null ? void 0 : recoveryError.status) || null,
960
+ errorCode: (recoveryError == null ? void 0 : recoveryError.code) || null
961
+ });
962
+ }
963
+ }
964
+ invalidateLocalSession();
965
+ }
966
+ };
967
+ void loadProfile();
968
+ return () => {
969
+ cancelled = true;
970
+ };
815
971
  }, [token]);
816
972
  const login2 = (clientKey, redirectUri, state) => {
817
973
  login(clientKey, redirectUri, state);
@@ -840,6 +996,7 @@ function AuthProvider({ children, onSessionExpired }) {
840
996
  setToken(newToken);
841
997
  setTokenState(newToken);
842
998
  setSessionValid(true);
999
+ if (newToken) setLoading(true);
843
1000
  },
844
1001
  clearToken: () => {
845
1002
  stopSessionSecurity();