UNPKG

@labdigital/federated-token-react

Version:
232 lines (231 loc) 6.41 kB
"use client"; // src/provider.tsx import { decodeJwt } from "jose"; import Cookie from "js-cookie"; import { createContext, useCallback, useContext, useEffect, useState } from "react"; import { jsx } from "react/jsx-runtime"; var TOKEN_VALID_BUFFER = 5 * 60; var DEFAULT_COOKIE_NAMES = { userData: "userData", guestData: "guestData", userRefreshTokenExists: "userRefreshTokenExists", guestRefreshTokenExists: "guestRefreshTokenExists" }; var AuthContext = createContext(void 0); function AuthProvider({ children, options }) { const [authState, setAuthState] = useState({ isAuthenticated: false, hasToken: false, values: null, loading: true }); const cookieNames = { ...DEFAULT_COOKIE_NAMES, ...options.cookieNames }; const updateAuthState = useCallback((token) => { if (token?.isAuthenticated) { setAuthState({ isAuthenticated: true, hasToken: true, values: token.values, loading: false }); } else { setAuthState({ isAuthenticated: false, hasToken: Boolean(token), values: null, loading: false }); } }, []); const checkTokenValidity = useCallback((token) => { const timeSec = Math.floor(Date.now() / 1e3); return Boolean(token?.exp && token.exp - TOKEN_VALID_BUFFER > timeSec); }, []); const getJWT = useCallback(() => { const userToken = Cookie.get(cookieNames.userData); const extractValues = (tokenPayload) => { const skipKeys = ["exp"]; return Object.keys(tokenPayload).reduce( (acc, key) => ( // biome-ignore lint/performance/noAccumulatingSpread: fixme skipKeys.includes(key) ? acc : { ...acc, [key]: tokenPayload[key] } ), {} ); }; if (userToken) { const tokenPayload = decodeToken(userToken); if (tokenPayload) { if (tokenPayload) { return { exp: tokenPayload.exp, isAuthenticated: true, values: extractValues(tokenPayload) }; } } } const guestToken = Cookie.get(cookieNames.guestData); if (guestToken) { const tokenPayload = decodeToken(guestToken); if (tokenPayload) { return { exp: tokenPayload.exp, isAuthenticated: false, values: extractValues(tokenPayload) }; } } }, [cookieNames.userData, cookieNames.guestData]); const checkLocalToken = useCallback(() => { const token = getJWT(); if (!token) { return void 0; } return checkTokenValidity(token) ? token : void 0; }, [getJWT, checkTokenValidity]); const validateLocalToken = useCallback(() => { const token = checkLocalToken(); updateAuthState(token); }, [updateAuthState, checkLocalToken]); const loadToken = useCallback(() => { const token = getJWT(); updateAuthState(token); }, [updateAuthState, getJWT]); const checkToken = useCallback(async () => { const token = await getAccessToken(); updateAuthState(token); }, [options.refreshTokenEndpoint, updateAuthState]); useEffect(() => { loadToken(); }, [loadToken]); const logout = async () => { setAuthState({ isAuthenticated: false, hasToken: false, values: null, loading: false }); await clearTokens(); validateLocalToken(); }; const getAccessToken = async () => { const token = getJWT(); const timeSec = Math.floor(Date.now() / 1e3); const buffer = 5 * 60; if (token?.exp && token.exp - buffer > timeSec) { return token; } const hasRefreshToken = Cookie.get(cookieNames.userRefreshTokenExists) ?? Cookie.get(cookieNames.guestRefreshTokenExists); if (hasRefreshToken) { const success = await refreshAccessToken(); if (success) { return getJWT(); } } return void 0; }; const refreshAccessToken = async (signal) => { if (!signal) { const timeout = options.refreshTimeoutMs ?? 1e4; signal = AbortSignal.timeout(timeout); } try { if (options.refreshHandler) { return await options.refreshHandler(signal); } if (!options.refreshTokenEndpoint || !options.refreshTokenMutation) { throw new Error("No refresh token endpoint or mutation provided"); } const response = await fetch(options.refreshTokenEndpoint, { method: "POST", body: options.refreshTokenMutation, headers: { "Content-Type": "application/json" }, credentials: "include", signal }); if (!response.ok) { throw new Error("Failed to refresh token"); } const data = await response.json(); if (!data) { throw new Error("Failed to refresh token"); } if (data.errors && data.errors.length > 0) { throw new Error("Failed to refresh token"); } return data; } catch (error) { if (error instanceof Error && error.name === "AbortError") { const timeout = options.refreshTimeoutMs ?? 1e4; throw new Error(`Token refresh timed out after ${timeout}ms`); } throw error; } }; const clearTokens = async () => { if (options.logoutHandler) { return options.logoutHandler(); } if (!options.logoutEndpoint || !options.logoutMutation) { throw new Error("No logout endpoint or mutation provided"); } const response = await fetch(options.logoutEndpoint, { method: "POST", body: options.logoutMutation, headers: { "Content-Type": "application/json" }, credentials: "include" }); if (!response.ok) { throw new Error(`Failed to clear token: ${response.statusText}`); } }; return /* @__PURE__ */ jsx( AuthContext.Provider, { value: { ...authState, logout, validateLocalToken, checkToken, refreshToken: refreshAccessToken }, children } ); } function useAuth() { const context = useContext(AuthContext); if (context === void 0) { throw new Error("useAuth must be used within an AuthProvider"); } return context; } var decodeToken = (token) => { const decodedToken = decodeJwt(token); if (!decodedToken) { return void 0; } return decodedToken; }; export { AuthProvider, useAuth }; //# sourceMappingURL=index.js.map