Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions web/frontend/src/features/auth/hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { useEffect, useRef, useCallback } from 'react';
import { useAuthStore } from '../store/authStore';

declare global {
interface Window {
__scriberr_original_fetch?: typeof window.fetch;
}
}

export function useAuth() {
const {
token,
Expand Down Expand Up @@ -49,8 +55,7 @@ export function useAuth() {
if (window.location.pathname !== "/") {
// Force navigation handled by RouterContext or window.location if critical
window.history.pushState({ route: { path: 'home' } }, "", "/");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.dispatchEvent(new PopStateEvent('popstate', { state: { route: { path: 'home' } } as any }));
window.dispatchEvent(new PopStateEvent('popstate', { state: { route: { path: 'home' } } }));
}
}, [token, storeLogout]);

Expand All @@ -63,7 +68,8 @@ export function useAuth() {

const tryRefresh = useCallback(async (): Promise<string | null> => {
try {
const res = await fetch('/api/v1/auth/refresh', { method: 'POST' })
const fetchToUse = window.__scriberr_original_fetch || window.fetch;
const res = await fetchToUse('/api/v1/auth/refresh', { method: 'POST' })
if (!res.ok) return null
const data = await res.json()
if (data?.token) {
Expand All @@ -80,28 +86,36 @@ export function useAuth() {
// Consolidated token management
useEffect(() => {
if (!fetchWrapperSetupRef.current) {
const originalFetch = window.fetch.bind(window);
if (!window.__scriberr_original_fetch) {
window.__scriberr_original_fetch = window.fetch.bind(window);
}

const originalFetch = window.__scriberr_original_fetch!;
const wrappedFetch: typeof window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
const url = typeof input === 'string' ? input : (input instanceof URL ? input.href : input.url);
const isAuthEndpoint = url.includes('/api/v1/auth/');

let res = await originalFetch(input, init);
if (res.status === 401) {
if (res.status === 401 && !isAuthEndpoint) {
const newToken = await tryRefresh()
if (newToken) {
const newInit: RequestInit | undefined = init ? { ...init } : undefined
if (newInit?.headers && typeof newInit.headers === 'object') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(newInit.headers as any)['Authorization'] = `Bearer ${newToken}`
}
const newInit: RequestInit = init ? { ...init } : {};
const headers = new Headers(newInit.headers);
headers.set('Authorization', `Bearer ${newToken}`);
newInit.headers = headers;

res = await originalFetch(input, newInit)
if (res.status !== 401) return res
}
logout()
}
return res;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.fetch = wrappedFetch as any;
window.fetch = wrappedFetch;
fetchWrapperSetupRef.current = true;
return () => { window.fetch = originalFetch; };
// Note: We don't restore originalFetch on unmount because other components
// also use useAuth and expect the wrapped version. This is a bit hacky
// but safer than multiple re-wrapping/unwrapping.
}

if (tokenCheckIntervalRef.current) clearInterval(tokenCheckIntervalRef.current);
Expand Down
Loading