Files
life-echo/app-expo/src/features/profile/hooks.ts
Kevin 5dac3efd52 feat(app-expo): conversation WS warmup, background pool, lifecycle
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>
2026-05-06 14:54:47 +08:00

123 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
});
}