Prefetch opening over WebSocket from the conversations list before navigation, with prepared-session handoff into the chat screen. Add a single-slot background pool so leaving chat (in-app) keeps the last session socket with UI callbacks stripped; dispose on app background and reconnect after resume when the chat screen is mounted. Tear down pooled sockets on logout, purge, and conversation delete. RealtimeSession supports attachUiCallbacks and idempotent dispose, and the chat composer hides the connection notice while connecting if assistant history already exists. Fix pause handler wiring in the conversation screen. Co-authored-by: Cursor <cursoragent@cursor.com>
123 lines
3.1 KiB
TypeScript
123 lines
3.1 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||
import { router } from 'expo-router';
|
||
|
||
import { tokenManager } from '@/core/auth/token-manager';
|
||
import { disposeAllBackgroundConversationWs } from '@/features/conversation/conversation-ws-background-pool';
|
||
import { authKeys } from '@/features/auth/hooks';
|
||
|
||
import { profileApi } from './api';
|
||
import type {
|
||
LegalDocType,
|
||
PurgeUserDataRequest,
|
||
SubmitFeedbackRequest,
|
||
UpdateProfileRequest,
|
||
} from './types';
|
||
|
||
const profileKeys = {
|
||
all: ['profile'] as const,
|
||
detail: () => [...profileKeys.all, 'detail'] as const,
|
||
plans: () => [...profileKeys.all, 'plans'] as const,
|
||
currentPlan: () => [...profileKeys.all, 'current-plan'] as const,
|
||
quota: () => [...profileKeys.all, 'quota'] as const,
|
||
faqs: () => [...profileKeys.all, 'faqs'] as const,
|
||
legal: (type: LegalDocType) => [...profileKeys.all, 'legal', type] as const,
|
||
} as const;
|
||
|
||
// ─── Profile ───
|
||
|
||
export function useProfile() {
|
||
return useQuery({
|
||
queryKey: profileKeys.detail(),
|
||
queryFn: () => profileApi.fetchProfile(),
|
||
});
|
||
}
|
||
|
||
export function useUpdateProfile() {
|
||
const queryClient = useQueryClient();
|
||
|
||
return useMutation({
|
||
mutationFn: (body: UpdateProfileRequest) => profileApi.updateProfile(body),
|
||
onSuccess: (data) => {
|
||
queryClient.setQueryData(profileKeys.detail(), data);
|
||
},
|
||
});
|
||
}
|
||
|
||
// ─── Plans ───
|
||
|
||
export function usePlans() {
|
||
return useQuery({
|
||
queryKey: profileKeys.plans(),
|
||
queryFn: () => profileApi.fetchPlans(),
|
||
staleTime: 10 * 60 * 1000,
|
||
});
|
||
}
|
||
|
||
export function useCurrentPlan() {
|
||
return useQuery({
|
||
queryKey: profileKeys.currentPlan(),
|
||
queryFn: () => profileApi.fetchCurrentPlan(),
|
||
});
|
||
}
|
||
|
||
// ─── Quota ───
|
||
|
||
export function useQuota() {
|
||
return useQuery({
|
||
queryKey: profileKeys.quota(),
|
||
queryFn: () => profileApi.checkQuota(),
|
||
});
|
||
}
|
||
|
||
// ─── FAQ ───
|
||
|
||
export function useFaqs() {
|
||
return useQuery({
|
||
queryKey: profileKeys.faqs(),
|
||
queryFn: () => profileApi.fetchFaqs(),
|
||
staleTime: 30 * 60 * 1000,
|
||
});
|
||
}
|
||
|
||
// ─── Feedback ───
|
||
|
||
export function useSubmitFeedback() {
|
||
return useMutation({
|
||
mutationFn: (body: SubmitFeedbackRequest) =>
|
||
profileApi.submitFeedback(body),
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 永久清空服务端业务数据;成功后服务端会吊销所有 refresh token,
|
||
* 因此仅清本地会话并跳转登录(不再调用 logout 接口)。
|
||
*/
|
||
export function usePurgeUserData() {
|
||
const queryClient = useQueryClient();
|
||
|
||
return useMutation({
|
||
mutationFn: (body: PurgeUserDataRequest) => profileApi.purgeUserData(body),
|
||
onSuccess: async () => {
|
||
disposeAllBackgroundConversationWs();
|
||
await tokenManager.clearTokens();
|
||
queryClient.clear();
|
||
queryClient.setQueryData(authKeys.tokenCheck, false);
|
||
router.replace('/(auth)/login');
|
||
},
|
||
});
|
||
}
|
||
|
||
// ─── Legal ───
|
||
|
||
export function useLegalDoc(
|
||
type: LegalDocType,
|
||
options?: { enabled?: boolean },
|
||
) {
|
||
return useQuery({
|
||
queryKey: profileKeys.legal(type),
|
||
queryFn: () => profileApi.fetchLegalDoc(type),
|
||
staleTime: Infinity,
|
||
enabled: options?.enabled ?? true,
|
||
});
|
||
}
|