UNPKG

@wener/console

Version:

Base console UI toolkit

168 lines (167 loc) 5.06 kB
import { useEffect, useRef } from "react"; import { getGlobalStates } from "@wener/utils"; import { createStore } from "zustand"; import { mutative } from "zustand-mutative"; import { useNetworkStatus } from "../../utils/NetworkStatus.js"; import { createStoreSelectorHook } from "../../zustand/index.js"; export const AuthStatus = { Init: "Init", Authenticated: "Authenticated", Unauthenticated: "Unauthenticated", Expired: "Expired", Loading: "Loading", Error: "Error", Locked: "Locked" }; function createAuthStore() { return createStore(mutative((setState, getState, store) => { return { status: AuthStatus.Init, setAuth(o) { setState((s) => { Object.assign(s, { status: AuthStatus.Authenticated, ...o, expiresAt: o.expiresAt ? new Date(o.expiresAt) : undefined }); }); }, reset() { setState((s) => { Object.assign(s, { status: AuthStatus.Unauthenticated, accessToken: undefined, refreshToken: undefined, expiresIn: undefined, expiresAt: undefined, error: undefined }); }); } }; })); } function useAuthSidecar({ store, actions: { refresh }, storage = localStorage }) { const { online } = useNetworkStatus(); // todo watch storage ? const checkAuth = async () => { const accessToken = storage.getItem("accessToken"); const refreshToken = storage.getItem("refreshToken") ?? undefined; if (accessToken) { try { const out = await refresh({ accessToken, refreshToken }); store.getState().setAuth(out); return true; } catch (e) { console.error("Failed to refresh token", e); } } // not authenticated or server error store.getState().reset(); return false; }; const authRef = useRef(); const doAuthCheck = () => { const state = store.getState(); // only check for init and authenticated switch (state.status) { case AuthStatus.Init: case AuthStatus.Authenticated: break; default: return; } // avoid race let current = authRef.current; if (current) { current.then((v) => { // authed, skip for now, will check for next if (v) { authRef.current = undefined; } else { // check again return current = checkAuth(); } return v; }); } else { authRef.current = checkAuth().then((v) => { // done authRef.current = undefined; return v; }); } }; // auth check useEffect(() => { if (!online) return; doAuthCheck(); const timer = setInterval(doAuthCheck, 5 * 60 * 1000); return () => { clearInterval(timer); }; }, [ store, online ]); // useAuthTokenPersist(store, storage); } const AuthStoreStateKey = "AuthStore"; export const AuthSidecar = (props) => { let store = getAuthStore(); useAuthSidecar({ store, ...props }); return null; }; function useAuthTokenPersist(store, storage) { const ref = useRef(storage); ref.current = storage; // persist token useEffect(() => { return store.subscribe((s) => { if (s.status !== AuthStatus.Authenticated) { return; } const { accessToken = "", refreshToken } = s; const storage = ref.current; if ((storage.getItem("accessToken") ?? "") === accessToken) { return; } accessToken ? storage.setItem("accessToken", accessToken) : deleteItem(storage, "accessToken"); refreshToken ? storage.setItem("refreshToken", refreshToken) : deleteItem(storage, "refreshToken"); }); }, [ store ]); } export function getAuthStore() { return getGlobalStates(AuthStoreStateKey, () => { return createAuthStore(); }); } export function getAuthState() { return getAuthStore().getState(); } export function getAccessToken() { return getAuthState().accessToken; } function deleteItem(s, key) { if ("removeItem" in s) { s.removeItem(key); } else { delete s[key]; } } export const useAuthStore = createStoreSelectorHook(getAuthStore); //# sourceMappingURL=AuthStore.js.map