ai-shit
This commit is contained in:
@@ -1,28 +1,19 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import type { MutableRefObject } from 'react';
|
||||
import type { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
|
||||
import { AxiosHeaders } from 'axios';
|
||||
import { clearAuthToken, logoutSession, refreshSession, setAuthToken } from '../../lib/apiClient';
|
||||
|
||||
type AppStatus = string;
|
||||
|
||||
type AppDispatch = (action: { type: string; [key: string]: unknown }) => void;
|
||||
|
||||
type NotifyApiError = (error: unknown, fallbackMessage: string, variant?: string) => void;
|
||||
|
||||
type SetStatusMessage = (message: string, variant?: string) => void;
|
||||
|
||||
type SetLoading = (state: boolean) => void;
|
||||
|
||||
interface RetryableAxiosRequestConfig extends InternalAxiosRequestConfig {
|
||||
_retry?: boolean;
|
||||
}
|
||||
|
||||
interface UseAuthManagerArgs {
|
||||
apiClient: AxiosInstance;
|
||||
token?: string | null;
|
||||
appStatus: AppStatus;
|
||||
appDispatch: AppDispatch;
|
||||
notifyApiError: NotifyApiError;
|
||||
setStatusMessage: SetStatusMessage;
|
||||
setLoading: SetLoading;
|
||||
}
|
||||
@@ -33,40 +24,23 @@ interface UseAuthManagerResult {
|
||||
handleLogout: () => Promise<void>;
|
||||
}
|
||||
|
||||
const ensureAxiosHeaders = (
|
||||
headers?: InternalAxiosRequestConfig['headers'],
|
||||
): AxiosHeaders => {
|
||||
if (headers instanceof AxiosHeaders) {
|
||||
return headers;
|
||||
}
|
||||
return AxiosHeaders.from(headers || {});
|
||||
};
|
||||
|
||||
const setHeaderAuthorization = (config: InternalAxiosRequestConfig, token: string): void => {
|
||||
const headers = ensureAxiosHeaders(config.headers);
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
config.headers = headers;
|
||||
};
|
||||
|
||||
const useAuthManager = ({
|
||||
apiClient,
|
||||
token,
|
||||
appStatus,
|
||||
appDispatch,
|
||||
notifyApiError,
|
||||
setStatusMessage,
|
||||
setLoading,
|
||||
}: UseAuthManagerArgs): UseAuthManagerResult => {
|
||||
const tokenRef = useRef<string | null>(token);
|
||||
const refreshPromiseRef = useRef<Promise<string> | null>(null);
|
||||
const initialRefreshAttemptedRef = useRef(Boolean(token));
|
||||
|
||||
const refreshAccessToken = useCallback(async (): Promise<string> => {
|
||||
console.log('[Auth] Attempting to refresh access token…');
|
||||
appDispatch({ type: 'TOKEN_REFRESH_START' });
|
||||
try {
|
||||
const { data } = await apiClient.post<{ access_token?: string; tenant?: unknown }>('/auth/refresh');
|
||||
const data = await refreshSession();
|
||||
if (data?.access_token) {
|
||||
setAuthToken(data.access_token);
|
||||
appDispatch({
|
||||
type: 'TOKEN_REFRESH_SUCCESS',
|
||||
token: data.access_token,
|
||||
@@ -81,7 +55,7 @@ const useAuthManager = ({
|
||||
appDispatch({ type: 'TOKEN_REFRESH_FAILURE', error: (error as Error)?.message || null });
|
||||
throw error;
|
||||
}
|
||||
}, [apiClient, appDispatch]);
|
||||
}, [appDispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
tokenRef.current = token;
|
||||
@@ -95,90 +69,19 @@ const useAuthManager = ({
|
||||
}
|
||||
}, [token, appStatus, refreshAccessToken]);
|
||||
|
||||
useEffect(() => {
|
||||
const requestInterceptor = apiClient.interceptors.request.use((config) => {
|
||||
const currentToken = tokenRef.current;
|
||||
if (currentToken) {
|
||||
const headers = ensureAxiosHeaders(config.headers);
|
||||
if (!headers.has('Authorization')) {
|
||||
headers.set('Authorization', `Bearer ${currentToken}`);
|
||||
}
|
||||
config.headers = headers;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
const responseInterceptor = apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const axiosError = error as AxiosError & { config?: RetryableAxiosRequestConfig };
|
||||
const { response, config } = axiosError;
|
||||
if (!response || !config) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const status = response.status;
|
||||
const url = String(config?.url ?? '');
|
||||
const isAuthRoute = url.includes('/auth/login') || url.includes('/auth/refresh');
|
||||
|
||||
if (status === 401 && !config._retry && !isAuthRoute) {
|
||||
console.warn('[Auth] 401 received for', url, '- attempting token refresh');
|
||||
|
||||
if (!refreshPromiseRef.current) {
|
||||
refreshPromiseRef.current = (async () => {
|
||||
try {
|
||||
return await refreshAccessToken();
|
||||
} finally {
|
||||
refreshPromiseRef.current = null;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
try {
|
||||
const newToken = await refreshPromiseRef.current;
|
||||
if (!newToken) {
|
||||
throw new Error('No token returned from refresh');
|
||||
}
|
||||
config._retry = true;
|
||||
setHeaderAuthorization(config, newToken);
|
||||
console.log('[Auth] Retrying original request', url);
|
||||
try {
|
||||
return await apiClient(config);
|
||||
} catch (retryError) {
|
||||
if ((retryError as AxiosError)?.response?.status === 401) {
|
||||
notifyApiError(retryError, 'Session expired. Please log in again.');
|
||||
}
|
||||
throw retryError;
|
||||
}
|
||||
} catch (refreshError) {
|
||||
console.warn('[Auth] Refresh failed, clearing session');
|
||||
notifyApiError(refreshError, 'Session expired. Please log in again.');
|
||||
return Promise.reject(refreshError);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
apiClient.interceptors.request.eject(requestInterceptor);
|
||||
apiClient.interceptors.response.eject(responseInterceptor);
|
||||
};
|
||||
}, [apiClient, notifyApiError, refreshAccessToken]);
|
||||
|
||||
const handleLogout = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await apiClient.post('/auth/logout');
|
||||
await logoutSession();
|
||||
} catch (error) {
|
||||
console.warn('[Auth] Failed to revoke refresh token during logout', error);
|
||||
} finally {
|
||||
clearAuthToken();
|
||||
setLoading(false);
|
||||
appDispatch({ type: 'LOGOUT' });
|
||||
setStatusMessage('Logged out.', 'info');
|
||||
}
|
||||
}, [apiClient, appDispatch, setLoading, setStatusMessage]);
|
||||
}, [appDispatch, setLoading, setStatusMessage]);
|
||||
|
||||
return { tokenRef, refreshAccessToken, handleLogout };
|
||||
};
|
||||
|
||||
@@ -18,7 +18,9 @@ import useApiError from '../useApiError';
|
||||
import TagManager from '../../tag_manager';
|
||||
import usePasskeys from '../../settings/usePasskeys';
|
||||
import { useManagementModals } from '../../app/useManagementModals';
|
||||
import { api, useAppDispatch, useAppState } from '../../app/appState';
|
||||
import { useAppDispatch, useAppState } from '../../app/appState';
|
||||
import { fetchAsset } from '../../lib/apiClient';
|
||||
import { useApi } from '../../app/ApiContext';
|
||||
import useWorkspaceSelection from '../../app/useWorkspaceSelection';
|
||||
import { useEntryPointer as useEntryPointerCore } from '../../documents/useEntryPointer';
|
||||
import { isTagTransferEvent } from '../../documents/tagTransfer';
|
||||
@@ -153,6 +155,7 @@ const useDocumentsWorkspace = ({
|
||||
tenant,
|
||||
tenants: tenantOptionsRaw = [],
|
||||
} = appState;
|
||||
const { client: apiClient } = useApi();
|
||||
|
||||
const tenantRecord = (tenant ?? null) as TenantOption | null;
|
||||
const tenantNameCandidate = tenantRecord?.name ?? tenantRecord?.slug ?? null;
|
||||
@@ -179,11 +182,9 @@ const useDocumentsWorkspace = ({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [creatingFolder, setCreatingFolder] = useState(false);
|
||||
const { tokenRef, handleLogout } = useAuthManager({
|
||||
apiClient: api,
|
||||
token,
|
||||
appStatus,
|
||||
appDispatch,
|
||||
notifyApiError,
|
||||
setStatusMessage,
|
||||
setLoading,
|
||||
});
|
||||
@@ -220,7 +221,11 @@ const useDocumentsWorkspace = ({
|
||||
const shellRef = useRef(null);
|
||||
const assetManagerRef = useRef(null);
|
||||
if (!assetManagerRef.current) {
|
||||
assetManagerRef.current = new AssetManager({ api, assetPresignTtlMs: ASSET_PRESIGN_TTL_MS });
|
||||
const fetcher = async (id: Identifier) => {
|
||||
const asset = await fetchAsset(id);
|
||||
return (asset as unknown) as any;
|
||||
};
|
||||
assetManagerRef.current = new AssetManager({ fetchAsset: fetcher, assetPresignTtlMs: ASSET_PRESIGN_TTL_MS });
|
||||
}
|
||||
const assetManager = assetManagerRef.current;
|
||||
|
||||
@@ -346,7 +351,7 @@ const useDocumentsWorkspace = ({
|
||||
isInvalidFolderDrop,
|
||||
} = useFolderTree({
|
||||
initialSelectedFolder: routeFolderId || 'root',
|
||||
apiClient: api,
|
||||
apiClient,
|
||||
tenantIdRef,
|
||||
documentsSortFieldRef: activeSortFieldRef,
|
||||
documentsSortDirectionRef: activeSortDirectionRef,
|
||||
@@ -369,7 +374,7 @@ const useDocumentsWorkspace = ({
|
||||
isFilterActive,
|
||||
documentsFilterValue,
|
||||
} = useDocumentsSearch({
|
||||
api,
|
||||
api: apiClient,
|
||||
token,
|
||||
selectedFolder,
|
||||
navigate,
|
||||
@@ -505,7 +510,7 @@ const useDocumentsWorkspace = ({
|
||||
handleTagDelete,
|
||||
setTags,
|
||||
} = useTags({
|
||||
apiClient: api,
|
||||
apiClient,
|
||||
notifyApiError,
|
||||
setStatusMessage,
|
||||
tagManager,
|
||||
@@ -549,7 +554,7 @@ const useDocumentsWorkspace = ({
|
||||
handleCorrespondentDelete,
|
||||
setCorrespondents,
|
||||
} = useCorrespondents({
|
||||
apiClient: api,
|
||||
apiClient,
|
||||
notifyApiError,
|
||||
setStatusMessage,
|
||||
tenantIdRef,
|
||||
@@ -604,7 +609,7 @@ const useDocumentsWorkspace = ({
|
||||
handleBulkTagRemoveFromDetail,
|
||||
handleBulkSelectionReanalyze,
|
||||
} = useDocumentTagging({
|
||||
apiClient: api,
|
||||
apiClient,
|
||||
tags,
|
||||
tagManager,
|
||||
refreshTags,
|
||||
@@ -623,7 +628,7 @@ const useDocumentsWorkspace = ({
|
||||
clearUploadQueue,
|
||||
resetUploadsState,
|
||||
} = useDocumentUploads({
|
||||
apiClient: api,
|
||||
apiClient,
|
||||
token,
|
||||
selectedFolder,
|
||||
currentFolderName,
|
||||
@@ -660,7 +665,7 @@ const useDocumentsWorkspace = ({
|
||||
handleCorrespondentRemove,
|
||||
handleCorrespondentAdd,
|
||||
} = useDocumentCorrespondentActions({
|
||||
apiClient: api,
|
||||
apiClient,
|
||||
correspondents,
|
||||
handleCorrespondentCreate,
|
||||
notifyApiError,
|
||||
@@ -1369,7 +1374,7 @@ const useDocumentsWorkspace = ({
|
||||
}, [missingBreadcrumbAncestors, ensureFolderData]);
|
||||
|
||||
const { handleTenantSelect } = useTenantManager({
|
||||
apiClient: api,
|
||||
apiClient,
|
||||
appDispatch,
|
||||
currentTenantId,
|
||||
resetWorkspaceState,
|
||||
|
||||
Reference in New Issue
Block a user