@convex-dev/better-auth
Version:
A Better Auth component for Convex.
96 lines • 3.87 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import isNetworkError from "is-network-error";
import { createContext, useCallback, useContext, useEffect, useMemo, } from "react";
const ConvexAuthInternalContext = createContext(undefined);
export function useAuth() {
return useContext(ConvexAuthInternalContext);
}
export function AuthProvider({ client, authClient, children, }) {
const { data: session, isPending: isSessionPending } = authClient.useSession();
const verbose = client.verbose ?? false;
const logVerbose = useCallback((message) => {
if (verbose) {
console.debug(`${new Date().toISOString()} ${message}`);
client.logger?.logVerbose(message);
}
}, [verbose]);
const fetchToken = useCallback(async () => {
const initialBackoff = 100;
const maxBackoff = 1000;
let retries = 0;
const nextBackoff = () => {
const baseBackoff = initialBackoff * Math.pow(2, retries);
retries += 1;
const actualBackoff = Math.min(baseBackoff, maxBackoff);
const jitter = actualBackoff * (Math.random() - 0.5);
return actualBackoff + jitter;
};
const fetchWithRetry = async () => {
try {
const { data } = await authClient.convex.token();
return data?.token || null;
}
catch (e) {
if (!isNetworkError(e)) {
throw e;
}
if (retries > 10) {
logVerbose(`fetchToken failed with network error, giving up`);
throw e;
}
const backoff = nextBackoff();
logVerbose(`fetchToken failed with network error, attempting retrying in ${backoff}ms`);
await new Promise((resolve) => setTimeout(resolve, backoff));
return fetchWithRetry();
}
};
return fetchWithRetry();
}, [client]);
const fetchAccessToken = useCallback(async ({ forceRefreshToken }) => {
if (forceRefreshToken) {
const token = await fetchToken();
logVerbose(`returning retrieved token`);
return token;
}
return null;
}, [fetchToken]);
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
(async () => {
const url = new URL(window.location?.href);
const token = url.searchParams.get("ott");
if (token) {
const authClientWithCrossDomain = authClient;
url.searchParams.delete("ott");
const result = await authClientWithCrossDomain.crossDomain.oneTimeToken.verify({
token,
});
const session = result.data?.session;
if (session) {
await authClient.getSession({
fetchOptions: {
headers: {
Authorization: `Bearer ${session.token}`,
},
},
});
authClientWithCrossDomain.updateSession();
}
window.history.replaceState({}, "", url);
}
})();
},
// Explicitly chosen dependencies.
// This effect should mostly only run once
// on mount.
[client, authClient]);
const isAuthenticated = session !== null;
const isLoading = isSessionPending;
const authState = useMemo(() => ({
isLoading,
isAuthenticated,
fetchAccessToken,
}), [fetchAccessToken, isLoading, isAuthenticated]);
return (_jsx(ConvexAuthInternalContext.Provider, { value: authState, children: children }));
}
//# sourceMappingURL=client.js.map