@tern-secure/nextjs 3.2.37 → 3.2.38
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/cjs/boundary/TernSecureClientProvider.js +28 -40
- package/dist/cjs/boundary/TernSecureClientProvider.js.map +1 -1
- package/dist/esm/boundary/TernSecureClientProvider.js +29 -41
- package/dist/esm/boundary/TernSecureClientProvider.js.map +1 -1
- package/dist/types/boundary/TernSecureClientProvider.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -38,7 +38,7 @@ function TernSecureClientProvider({
|
|
|
38
38
|
const auth = (0, import_react.useMemo)(() => import_client_init.ternSecureAuth, []);
|
|
39
39
|
const router = (0, import_navigation.useRouter)();
|
|
40
40
|
const pathname = (0, import_navigation.usePathname)();
|
|
41
|
-
const
|
|
41
|
+
const lastTokenCheckRef = (0, import_react.useRef)(0);
|
|
42
42
|
const [authState, setAuthState] = (0, import_react.useState)(() => ({
|
|
43
43
|
userId: null,
|
|
44
44
|
isLoaded: false,
|
|
@@ -47,10 +47,6 @@ function TernSecureClientProvider({
|
|
|
47
47
|
token: null
|
|
48
48
|
}));
|
|
49
49
|
const handleSignOut = (0, import_react.useCallback)(async (error) => {
|
|
50
|
-
if (intervalRef.current) {
|
|
51
|
-
clearInterval(intervalRef.current);
|
|
52
|
-
intervalRef.current = null;
|
|
53
|
-
}
|
|
54
50
|
await auth.signOut();
|
|
55
51
|
setAuthState({
|
|
56
52
|
isLoaded: true,
|
|
@@ -61,36 +57,33 @@ function TernSecureClientProvider({
|
|
|
61
57
|
});
|
|
62
58
|
router.push(loginPath);
|
|
63
59
|
}, [auth, router, loginPath]);
|
|
64
|
-
const checkTokenValidity = (0, import_react.useCallback)(async (user) => {
|
|
60
|
+
const checkTokenValidity = (0, import_react.useCallback)(async (user, forceCheck = false) => {
|
|
65
61
|
if (user) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const timeSinceLastCheck = now - lastTokenCheckRef.current;
|
|
64
|
+
if (forceCheck || timeSinceLastCheck > 5 * 60 * 1e3) {
|
|
65
|
+
try {
|
|
66
|
+
const token = await user.getIdToken(forceCheck);
|
|
67
|
+
const decodedToken = await (0, import_sessionTernSecure.verifyTernIdToken)(token);
|
|
68
|
+
const isValid = decodedToken.valid;
|
|
69
|
+
lastTokenCheckRef.current = now;
|
|
70
|
+
if (isValid) {
|
|
71
|
+
return { isValid: true, token, userId: user.uid };
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("Token validation error:", error);
|
|
75
|
+
await handleSignOut(error instanceof Error ? error : new Error("Authentication token is invalid"));
|
|
76
|
+
return { isValid: false, token: null, userId: null };
|
|
72
77
|
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
await handleSignOut(error instanceof Error ? error : new Error("Authentication token is invalid"));
|
|
76
|
-
return { isValid: false, token: null, userId: null };
|
|
78
|
+
} else {
|
|
79
|
+
return { isValid: true, token: authState.token, userId: user.uid };
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
return { isValid: false, token: null, userId: null };
|
|
80
|
-
}, [handleSignOut]);
|
|
81
|
-
const startTokenCheckInterval = (0, import_react.useCallback)(() => {
|
|
82
|
-
if (intervalRef.current) {
|
|
83
|
-
clearInterval(intervalRef.current);
|
|
84
|
-
}
|
|
85
|
-
if (pathname !== loginPath) {
|
|
86
|
-
intervalRef.current = setInterval(() => {
|
|
87
|
-
checkTokenValidity(auth.currentUser);
|
|
88
|
-
}, 3e4);
|
|
89
|
-
}
|
|
90
|
-
}, [auth, checkTokenValidity, loginPath, pathname]);
|
|
83
|
+
}, [handleSignOut, authState.token]);
|
|
91
84
|
const handleAuthStateChange = (0, import_react.useCallback)(async (user) => {
|
|
92
85
|
try {
|
|
93
|
-
const { isValid, token, userId } = await checkTokenValidity(user);
|
|
86
|
+
const { isValid, token, userId } = await checkTokenValidity(user, true);
|
|
94
87
|
setAuthState({
|
|
95
88
|
isLoaded: true,
|
|
96
89
|
userId,
|
|
@@ -104,12 +97,6 @@ function TernSecureClientProvider({
|
|
|
104
97
|
if (!isValid && pathname !== loginPath) {
|
|
105
98
|
router.push(loginPath);
|
|
106
99
|
}
|
|
107
|
-
if (user && isValid) {
|
|
108
|
-
startTokenCheckInterval();
|
|
109
|
-
} else if (intervalRef.current) {
|
|
110
|
-
clearInterval(intervalRef.current);
|
|
111
|
-
intervalRef.current = null;
|
|
112
|
-
}
|
|
113
100
|
} catch (error) {
|
|
114
101
|
console.error("Auth state change error:", error);
|
|
115
102
|
setAuthState({
|
|
@@ -120,21 +107,22 @@ function TernSecureClientProvider({
|
|
|
120
107
|
error: error instanceof Error ? error : new Error("An unknown error occurred")
|
|
121
108
|
});
|
|
122
109
|
}
|
|
123
|
-
}, [checkTokenValidity, onUserChanged, router, loginPath, pathname
|
|
110
|
+
}, [checkTokenValidity, onUserChanged, router, loginPath, pathname]);
|
|
124
111
|
(0, import_react.useEffect)(() => {
|
|
125
112
|
const unsubscribeAuthState = (0, import_auth.onAuthStateChanged)(auth, handleAuthStateChange);
|
|
113
|
+
const unsubscribeTokenChanged = (0, import_auth.onIdTokenChanged)(auth, handleAuthStateChange);
|
|
126
114
|
return () => {
|
|
127
115
|
unsubscribeAuthState();
|
|
128
|
-
|
|
129
|
-
clearInterval(intervalRef.current);
|
|
130
|
-
}
|
|
116
|
+
unsubscribeTokenChanged();
|
|
131
117
|
};
|
|
132
118
|
}, [auth, handleAuthStateChange]);
|
|
133
119
|
const contextValue = (0, import_react.useMemo)(() => ({
|
|
134
120
|
...authState,
|
|
135
|
-
checkTokenValidity: () =>
|
|
121
|
+
checkTokenValidity: async () => {
|
|
122
|
+
await checkTokenValidity(auth.currentUser, true);
|
|
123
|
+
},
|
|
136
124
|
signOut: handleSignOut
|
|
137
|
-
}), [authState,
|
|
125
|
+
}), [authState, checkTokenValidity, auth, handleSignOut]);
|
|
138
126
|
if (!authState.isLoaded) {
|
|
139
127
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TernSecureCtx.TernSecureCtx.Provider, { value: contextValue, children: loadingComponent || /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-live": "polite", "aria-busy": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sr-only", children: "Loading authentication state..." }) }) });
|
|
140
128
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"sourcesContent":["\"use client\"\r\n\r\nimport React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'\r\nimport { ternSecureAuth } from '../utils/client-init'\r\nimport { User, onAuthStateChanged } from \"firebase/auth\"\r\nimport { TernSecureCtx, TernSecureState, TernSecureCtxValue } from './TernSecureCtx'\r\nimport { useRouter, usePathname } from 'next/navigation'\r\nimport { verifyTernIdToken } from '../app-router/server/sessionTernSecure'\r\n\r\ninterface TernSecureClientProviderProps {\r\n children: React.ReactNode;\r\n onUserChanged?: (user: User | null) => Promise<void>;\r\n loginPath?: string;\r\n loadingComponent?: React.ReactNode;\r\n}\r\n\r\nexport function TernSecureClientProvider({ \r\n children, \r\n onUserChanged,\r\n loginPath = '/sign-in',\r\n loadingComponent\r\n}: TernSecureClientProviderProps) {\r\n const auth = useMemo(() => ternSecureAuth, []);\r\n const router = useRouter();\r\n const pathname = usePathname();\r\n const
|
|
1
|
+
{"version":3,"sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"sourcesContent":["\"use client\"\r\n\r\nimport React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'\r\nimport { ternSecureAuth } from '../utils/client-init'\r\nimport { User, onAuthStateChanged, onIdTokenChanged } from \"firebase/auth\"\r\nimport { TernSecureCtx, TernSecureState, TernSecureCtxValue } from './TernSecureCtx'\r\nimport { useRouter, usePathname } from 'next/navigation'\r\nimport { verifyTernIdToken } from '../app-router/server/sessionTernSecure'\r\n\r\ninterface TernSecureClientProviderProps {\r\n children: React.ReactNode;\r\n onUserChanged?: (user: User | null) => Promise<void>;\r\n loginPath?: string;\r\n loadingComponent?: React.ReactNode;\r\n}\r\n\r\nexport function TernSecureClientProvider({ \r\n children, \r\n onUserChanged,\r\n loginPath = '/sign-in',\r\n loadingComponent\r\n}: TernSecureClientProviderProps) {\r\n const auth = useMemo(() => ternSecureAuth, []);\r\n const router = useRouter();\r\n const pathname = usePathname();\r\n const lastTokenCheckRef = useRef<number>(0);\r\n\r\n const [authState, setAuthState] = useState<TernSecureState>(() => ({\r\n userId: null,\r\n isLoaded: false,\r\n error: null,\r\n isValid: false,\r\n token: null\r\n }));\r\n\r\n const handleSignOut = useCallback(async (error?: Error) => {\r\n await auth.signOut();\r\n setAuthState({\r\n isLoaded: true,\r\n userId: null,\r\n error: error || null,\r\n isValid: false,\r\n token: null\r\n });\r\n router.push(loginPath);\r\n }, [auth, router, loginPath]);\r\n\r\n const checkTokenValidity = useCallback(async (user: User | null, forceCheck: boolean = false) => {\r\n if (user) {\r\n const now = Date.now();\r\n const timeSinceLastCheck = now - lastTokenCheckRef.current;\r\n\r\n // Only check if forced or if it's been more than 5 minutes since the last check\r\n if (forceCheck || timeSinceLastCheck > 5 * 60 * 1000) {\r\n try {\r\n const token = await user.getIdToken(forceCheck);\r\n const decodedToken = await verifyTernIdToken(token);\r\n const isValid = decodedToken.valid;\r\n\r\n lastTokenCheckRef.current = now;\r\n\r\n if (isValid) {\r\n return { isValid: true, token, userId: user.uid };\r\n }\r\n } catch (error) {\r\n console.error('Token validation error:', error);\r\n await handleSignOut(error instanceof Error ? error : new Error('Authentication token is invalid'));\r\n return { isValid: false, token: null, userId: null };\r\n }\r\n } else {\r\n // If we're not checking, assume the token is still valid\r\n return { isValid: true, token: authState.token, userId: user.uid };\r\n }\r\n }\r\n return { isValid: false, token: null, userId: null };\r\n }, [handleSignOut, authState.token]);\r\n\r\n const handleAuthStateChange = useCallback(async (user: User | null) => {\r\n try {\r\n const { isValid, token, userId } = await checkTokenValidity(user, true);\r\n \r\n setAuthState({\r\n isLoaded: true,\r\n userId,\r\n isValid,\r\n token,\r\n error: null\r\n });\r\n\r\n if (onUserChanged) {\r\n await onUserChanged(user);\r\n }\r\n\r\n if (!isValid && pathname !== loginPath) {\r\n router.push(loginPath);\r\n }\r\n } catch (error) {\r\n console.error('Auth state change error:', error);\r\n setAuthState({\r\n isLoaded: true,\r\n userId: null,\r\n isValid: false,\r\n token: null,\r\n error: error instanceof Error ? error : new Error('An unknown error occurred')\r\n });\r\n }\r\n }, [checkTokenValidity, onUserChanged, router, loginPath, pathname]);\r\n\r\n useEffect(() => {\r\n const unsubscribeAuthState = onAuthStateChanged(auth, handleAuthStateChange);\r\n const unsubscribeTokenChanged = onIdTokenChanged(auth, handleAuthStateChange);\r\n \r\n return () => {\r\n unsubscribeAuthState();\r\n unsubscribeTokenChanged();\r\n };\r\n }, [auth, handleAuthStateChange]);\r\n\r\n const contextValue: TernSecureCtxValue = useMemo(() => ({\r\n ...authState,\r\n checkTokenValidity: async () => {\r\n await checkTokenValidity(auth.currentUser, true);\r\n },\r\n signOut: handleSignOut,\r\n }), [authState, checkTokenValidity, auth, handleSignOut]);\r\n \r\n if (!authState.isLoaded) {\r\n return (\r\n <TernSecureCtx.Provider value={contextValue}>\r\n {loadingComponent || (\r\n <div aria-live=\"polite\" aria-busy=\"true\">\r\n <span className=\"sr-only\">Loading authentication state...</span>\r\n </div>\r\n )}\r\n </TernSecureCtx.Provider>\r\n );\r\n }\r\n\r\n return (\r\n <TernSecureCtx.Provider value={contextValue}>\r\n {children}\r\n </TernSecureCtx.Provider>\r\n );\r\n}\r\n\r\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
|
4
4
|
import { ternSecureAuth } from "../utils/client-init";
|
|
5
|
-
import { onAuthStateChanged } from "firebase/auth";
|
|
5
|
+
import { onAuthStateChanged, onIdTokenChanged } from "firebase/auth";
|
|
6
6
|
import { TernSecureCtx } from "./TernSecureCtx";
|
|
7
7
|
import { useRouter, usePathname } from "next/navigation";
|
|
8
8
|
import { verifyTernIdToken } from "../app-router/server/sessionTernSecure";
|
|
@@ -15,7 +15,7 @@ function TernSecureClientProvider({
|
|
|
15
15
|
const auth = useMemo(() => ternSecureAuth, []);
|
|
16
16
|
const router = useRouter();
|
|
17
17
|
const pathname = usePathname();
|
|
18
|
-
const
|
|
18
|
+
const lastTokenCheckRef = useRef(0);
|
|
19
19
|
const [authState, setAuthState] = useState(() => ({
|
|
20
20
|
userId: null,
|
|
21
21
|
isLoaded: false,
|
|
@@ -24,10 +24,6 @@ function TernSecureClientProvider({
|
|
|
24
24
|
token: null
|
|
25
25
|
}));
|
|
26
26
|
const handleSignOut = useCallback(async (error) => {
|
|
27
|
-
if (intervalRef.current) {
|
|
28
|
-
clearInterval(intervalRef.current);
|
|
29
|
-
intervalRef.current = null;
|
|
30
|
-
}
|
|
31
27
|
await auth.signOut();
|
|
32
28
|
setAuthState({
|
|
33
29
|
isLoaded: true,
|
|
@@ -38,36 +34,33 @@ function TernSecureClientProvider({
|
|
|
38
34
|
});
|
|
39
35
|
router.push(loginPath);
|
|
40
36
|
}, [auth, router, loginPath]);
|
|
41
|
-
const checkTokenValidity = useCallback(async (user) => {
|
|
37
|
+
const checkTokenValidity = useCallback(async (user, forceCheck = false) => {
|
|
42
38
|
if (user) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const timeSinceLastCheck = now - lastTokenCheckRef.current;
|
|
41
|
+
if (forceCheck || timeSinceLastCheck > 5 * 60 * 1e3) {
|
|
42
|
+
try {
|
|
43
|
+
const token = await user.getIdToken(forceCheck);
|
|
44
|
+
const decodedToken = await verifyTernIdToken(token);
|
|
45
|
+
const isValid = decodedToken.valid;
|
|
46
|
+
lastTokenCheckRef.current = now;
|
|
47
|
+
if (isValid) {
|
|
48
|
+
return { isValid: true, token, userId: user.uid };
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error("Token validation error:", error);
|
|
52
|
+
await handleSignOut(error instanceof Error ? error : new Error("Authentication token is invalid"));
|
|
53
|
+
return { isValid: false, token: null, userId: null };
|
|
49
54
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
await handleSignOut(error instanceof Error ? error : new Error("Authentication token is invalid"));
|
|
53
|
-
return { isValid: false, token: null, userId: null };
|
|
55
|
+
} else {
|
|
56
|
+
return { isValid: true, token: authState.token, userId: user.uid };
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
return { isValid: false, token: null, userId: null };
|
|
57
|
-
}, [handleSignOut]);
|
|
58
|
-
const startTokenCheckInterval = useCallback(() => {
|
|
59
|
-
if (intervalRef.current) {
|
|
60
|
-
clearInterval(intervalRef.current);
|
|
61
|
-
}
|
|
62
|
-
if (pathname !== loginPath) {
|
|
63
|
-
intervalRef.current = setInterval(() => {
|
|
64
|
-
checkTokenValidity(auth.currentUser);
|
|
65
|
-
}, 3e4);
|
|
66
|
-
}
|
|
67
|
-
}, [auth, checkTokenValidity, loginPath, pathname]);
|
|
60
|
+
}, [handleSignOut, authState.token]);
|
|
68
61
|
const handleAuthStateChange = useCallback(async (user) => {
|
|
69
62
|
try {
|
|
70
|
-
const { isValid, token, userId } = await checkTokenValidity(user);
|
|
63
|
+
const { isValid, token, userId } = await checkTokenValidity(user, true);
|
|
71
64
|
setAuthState({
|
|
72
65
|
isLoaded: true,
|
|
73
66
|
userId,
|
|
@@ -81,12 +74,6 @@ function TernSecureClientProvider({
|
|
|
81
74
|
if (!isValid && pathname !== loginPath) {
|
|
82
75
|
router.push(loginPath);
|
|
83
76
|
}
|
|
84
|
-
if (user && isValid) {
|
|
85
|
-
startTokenCheckInterval();
|
|
86
|
-
} else if (intervalRef.current) {
|
|
87
|
-
clearInterval(intervalRef.current);
|
|
88
|
-
intervalRef.current = null;
|
|
89
|
-
}
|
|
90
77
|
} catch (error) {
|
|
91
78
|
console.error("Auth state change error:", error);
|
|
92
79
|
setAuthState({
|
|
@@ -97,21 +84,22 @@ function TernSecureClientProvider({
|
|
|
97
84
|
error: error instanceof Error ? error : new Error("An unknown error occurred")
|
|
98
85
|
});
|
|
99
86
|
}
|
|
100
|
-
}, [checkTokenValidity, onUserChanged, router, loginPath, pathname
|
|
87
|
+
}, [checkTokenValidity, onUserChanged, router, loginPath, pathname]);
|
|
101
88
|
useEffect(() => {
|
|
102
89
|
const unsubscribeAuthState = onAuthStateChanged(auth, handleAuthStateChange);
|
|
90
|
+
const unsubscribeTokenChanged = onIdTokenChanged(auth, handleAuthStateChange);
|
|
103
91
|
return () => {
|
|
104
92
|
unsubscribeAuthState();
|
|
105
|
-
|
|
106
|
-
clearInterval(intervalRef.current);
|
|
107
|
-
}
|
|
93
|
+
unsubscribeTokenChanged();
|
|
108
94
|
};
|
|
109
95
|
}, [auth, handleAuthStateChange]);
|
|
110
96
|
const contextValue = useMemo(() => ({
|
|
111
97
|
...authState,
|
|
112
|
-
checkTokenValidity: () =>
|
|
98
|
+
checkTokenValidity: async () => {
|
|
99
|
+
await checkTokenValidity(auth.currentUser, true);
|
|
100
|
+
},
|
|
113
101
|
signOut: handleSignOut
|
|
114
|
-
}), [authState,
|
|
102
|
+
}), [authState, checkTokenValidity, auth, handleSignOut]);
|
|
115
103
|
if (!authState.isLoaded) {
|
|
116
104
|
return /* @__PURE__ */ jsx(TernSecureCtx.Provider, { value: contextValue, children: loadingComponent || /* @__PURE__ */ jsx("div", { "aria-live": "polite", "aria-busy": "true", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Loading authentication state..." }) }) });
|
|
117
105
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"sourcesContent":["\"use client\"\r\n\r\nimport React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'\r\nimport { ternSecureAuth } from '../utils/client-init'\r\nimport { User, onAuthStateChanged } from \"firebase/auth\"\r\nimport { TernSecureCtx, TernSecureState, TernSecureCtxValue } from './TernSecureCtx'\r\nimport { useRouter, usePathname } from 'next/navigation'\r\nimport { verifyTernIdToken } from '../app-router/server/sessionTernSecure'\r\n\r\ninterface TernSecureClientProviderProps {\r\n children: React.ReactNode;\r\n onUserChanged?: (user: User | null) => Promise<void>;\r\n loginPath?: string;\r\n loadingComponent?: React.ReactNode;\r\n}\r\n\r\nexport function TernSecureClientProvider({ \r\n children, \r\n onUserChanged,\r\n loginPath = '/sign-in',\r\n loadingComponent\r\n}: TernSecureClientProviderProps) {\r\n const auth = useMemo(() => ternSecureAuth, []);\r\n const router = useRouter();\r\n const pathname = usePathname();\r\n const
|
|
1
|
+
{"version":3,"sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"sourcesContent":["\"use client\"\r\n\r\nimport React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'\r\nimport { ternSecureAuth } from '../utils/client-init'\r\nimport { User, onAuthStateChanged, onIdTokenChanged } from \"firebase/auth\"\r\nimport { TernSecureCtx, TernSecureState, TernSecureCtxValue } from './TernSecureCtx'\r\nimport { useRouter, usePathname } from 'next/navigation'\r\nimport { verifyTernIdToken } from '../app-router/server/sessionTernSecure'\r\n\r\ninterface TernSecureClientProviderProps {\r\n children: React.ReactNode;\r\n onUserChanged?: (user: User | null) => Promise<void>;\r\n loginPath?: string;\r\n loadingComponent?: React.ReactNode;\r\n}\r\n\r\nexport function TernSecureClientProvider({ \r\n children, \r\n onUserChanged,\r\n loginPath = '/sign-in',\r\n loadingComponent\r\n}: TernSecureClientProviderProps) {\r\n const auth = useMemo(() => ternSecureAuth, []);\r\n const router = useRouter();\r\n const pathname = usePathname();\r\n const lastTokenCheckRef = useRef<number>(0);\r\n\r\n const [authState, setAuthState] = useState<TernSecureState>(() => ({\r\n userId: null,\r\n isLoaded: false,\r\n error: null,\r\n isValid: false,\r\n token: null\r\n }));\r\n\r\n const handleSignOut = useCallback(async (error?: Error) => {\r\n await auth.signOut();\r\n setAuthState({\r\n isLoaded: true,\r\n userId: null,\r\n error: error || null,\r\n isValid: false,\r\n token: null\r\n });\r\n router.push(loginPath);\r\n }, [auth, router, loginPath]);\r\n\r\n const checkTokenValidity = useCallback(async (user: User | null, forceCheck: boolean = false) => {\r\n if (user) {\r\n const now = Date.now();\r\n const timeSinceLastCheck = now - lastTokenCheckRef.current;\r\n\r\n // Only check if forced or if it's been more than 5 minutes since the last check\r\n if (forceCheck || timeSinceLastCheck > 5 * 60 * 1000) {\r\n try {\r\n const token = await user.getIdToken(forceCheck);\r\n const decodedToken = await verifyTernIdToken(token);\r\n const isValid = decodedToken.valid;\r\n\r\n lastTokenCheckRef.current = now;\r\n\r\n if (isValid) {\r\n return { isValid: true, token, userId: user.uid };\r\n }\r\n } catch (error) {\r\n console.error('Token validation error:', error);\r\n await handleSignOut(error instanceof Error ? error : new Error('Authentication token is invalid'));\r\n return { isValid: false, token: null, userId: null };\r\n }\r\n } else {\r\n // If we're not checking, assume the token is still valid\r\n return { isValid: true, token: authState.token, userId: user.uid };\r\n }\r\n }\r\n return { isValid: false, token: null, userId: null };\r\n }, [handleSignOut, authState.token]);\r\n\r\n const handleAuthStateChange = useCallback(async (user: User | null) => {\r\n try {\r\n const { isValid, token, userId } = await checkTokenValidity(user, true);\r\n \r\n setAuthState({\r\n isLoaded: true,\r\n userId,\r\n isValid,\r\n token,\r\n error: null\r\n });\r\n\r\n if (onUserChanged) {\r\n await onUserChanged(user);\r\n }\r\n\r\n if (!isValid && pathname !== loginPath) {\r\n router.push(loginPath);\r\n }\r\n } catch (error) {\r\n console.error('Auth state change error:', error);\r\n setAuthState({\r\n isLoaded: true,\r\n userId: null,\r\n isValid: false,\r\n token: null,\r\n error: error instanceof Error ? error : new Error('An unknown error occurred')\r\n });\r\n }\r\n }, [checkTokenValidity, onUserChanged, router, loginPath, pathname]);\r\n\r\n useEffect(() => {\r\n const unsubscribeAuthState = onAuthStateChanged(auth, handleAuthStateChange);\r\n const unsubscribeTokenChanged = onIdTokenChanged(auth, handleAuthStateChange);\r\n \r\n return () => {\r\n unsubscribeAuthState();\r\n unsubscribeTokenChanged();\r\n };\r\n }, [auth, handleAuthStateChange]);\r\n\r\n const contextValue: TernSecureCtxValue = useMemo(() => ({\r\n ...authState,\r\n checkTokenValidity: async () => {\r\n await checkTokenValidity(auth.currentUser, true);\r\n },\r\n signOut: handleSignOut,\r\n }), [authState, checkTokenValidity, auth, handleSignOut]);\r\n \r\n if (!authState.isLoaded) {\r\n return (\r\n <TernSecureCtx.Provider value={contextValue}>\r\n {loadingComponent || (\r\n <div aria-live=\"polite\" aria-busy=\"true\">\r\n <span className=\"sr-only\">Loading authentication state...</span>\r\n </div>\r\n )}\r\n </TernSecureCtx.Provider>\r\n );\r\n }\r\n\r\n return (\r\n <TernSecureCtx.Provider value={contextValue}>\r\n {children}\r\n </TernSecureCtx.Provider>\r\n );\r\n}\r\n\r\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TernSecureClientProvider.d.ts","sourceRoot":"","sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAEhF,OAAO,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"TernSecureClientProvider.d.ts","sourceRoot":"","sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAEhF,OAAO,EAAE,IAAI,EAAwC,MAAM,eAAe,CAAA;AAK1E,UAAU,6BAA6B;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC;AAED,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,aAAa,EACb,SAAsB,EACtB,gBAAgB,EACjB,EAAE,6BAA6B,2CA0H/B"}
|