fix: close module access lifecycle gaps

This commit is contained in:
2026-08-17 09:15:37 +08:00
parent d16922f18c
commit ebe55ba17a
8 changed files with 289 additions and 19 deletions

View File

@@ -35,6 +35,7 @@ import {
AI_MODULE_SELECTION_PATH,
getGuardedAiModuleForPath,
isAiModuleAllowed,
isProgrammingProviderRoute,
} from './lib/ai-modules';
import { useUserSyncStore } from './stores/user-sync';
import { flushPendingAgentSessionSync } from '@/lib/agent-session-sync';
@@ -122,10 +123,6 @@ function getReturnPath(location: ReturnType<typeof useLocation>): string {
return `${location.pathname}${location.search}`;
}
function isProgrammingRoute(pathname: string): boolean {
return getGuardedAiModuleForPath(pathname) === 'programming';
}
function ProtectedLayout({
authReady,
authRequired,
@@ -321,10 +318,24 @@ function App() {
useEffect(() => {
if (rendererOnlyPreview) return;
if (!setupReady) return;
if (!programmingModuleAllowed) return;
if (!isProgrammingRoute(location.pathname)) return;
if (authRequired && !authInitialized) return;
if (authRequired && !authenticated) return;
if (!isProgrammingProviderRoute(location.pathname)) return;
if (
getGuardedAiModuleForPath(location.pathname) === 'programming'
&& !programmingModuleAllowed
) return;
initProviders();
}, [initProviders, location.pathname, programmingModuleAllowed, rendererOnlyPreview, setupReady]);
}, [
authInitialized,
authRequired,
authenticated,
initProviders,
location.pathname,
programmingModuleAllowed,
rendererOnlyPreview,
setupReady,
]);
useEffect(() => {
if (rendererOnlyPreview) return;

View File

@@ -76,6 +76,10 @@ const PROGRAMMING_ROUTE_PREFIXES = [
'/projects',
'/sessions',
'/models',
] as const;
const PROGRAMMING_PROVIDER_ROUTE_PREFIXES = [
...PROGRAMMING_ROUTE_PREFIXES,
'/settings',
] as const;
@@ -87,6 +91,10 @@ export function isAiModuleAllowed(moduleId: AiModuleId, access: ModuleAccess): b
return access[moduleAccessKeyById[moduleId]];
}
export function isProgrammingProviderRoute(pathname: string): boolean {
return PROGRAMMING_PROVIDER_ROUTE_PREFIXES.some((route) => matchesRoute(pathname, route));
}
export function getGuardedAiModuleForPath(pathname: string): AiModuleId | null {
if (pathname === '/image-canvas'
|| pathname.startsWith('/image-canvas/')

View File

@@ -236,7 +236,8 @@ async function readCurrentModuleAccess(fallback: ModuleAccess): Promise<ModuleAc
const response = await hostApiFetch<ModuleAccessResponse>('/api/auth/me');
if (!response.success) return fallback;
return normalizeModuleAccess(response.moduleAccess);
} catch {
} catch (error) {
if (isTerminalAuthError(error)) throw error;
return fallback;
}
}
@@ -347,9 +348,25 @@ export const useAuthStore = create<AuthState>()(
return;
}
const moduleAccess = await readCurrentModuleAccess(
normalizeModuleAccess(state.moduleAccess),
);
let moduleAccess: ModuleAccess;
try {
moduleAccess = await readCurrentModuleAccess(
normalizeModuleAccess(state.moduleAccess),
);
} catch (error) {
if (!isCurrentAuthSessionEpoch(operationEpoch)) return;
if (isTerminalAuthError(error)) {
advanceAuthSessionEpoch();
set({
initialized: true,
loading: false,
error: '登录已过期,请重新授权。',
...getClearedSession(),
});
return;
}
moduleAccess = normalizeModuleAccess(state.moduleAccess);
}
if (!isCurrentAuthSessionEpoch(operationEpoch)) return;
set({ initialized: true, loading: false, error: null, moduleAccess });
},
@@ -383,7 +400,10 @@ export const useAuthStore = create<AuthState>()(
});
} catch (error) {
if (!isCurrentAuthSessionEpoch(operationEpoch)) return;
const message = error instanceof Error ? error.message : String(error);
const terminal = isTerminalAuthError(error);
const message = terminal
? '登录已过期,请重新授权。'
: (error instanceof Error ? error.message : String(error));
advanceAuthSessionEpoch();
set({ loading: false, error: message, ...getClearedSession() });
throw new Error(message, { cause: error });
@@ -431,8 +451,10 @@ export const useAuthStore = create<AuthState>()(
return session.accessToken;
} catch (error) {
if (!isCurrentAuthSessionEpoch(operationEpoch)) return null;
const message = error instanceof Error ? error.message : String(error);
const terminal = isTerminalAuthError(error);
const message = terminal
? '登录已过期,请重新授权。'
: (error instanceof Error ? error.message : String(error));
if (terminal) advanceAuthSessionEpoch();
set({
initialized: true,