@wener/console
Version:
Base console UI toolkit
168 lines (167 loc) • 5.06 kB
JavaScript
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