enhance: 优化
This commit is contained in:
parent
3bc8dda46f
commit
ec83f9ce34
@ -10,7 +10,7 @@ import { fetchApi } from '@/lib/server-api-util';
|
|||||||
import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util';
|
import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util';
|
||||||
import { TransitionPresets } from '@react-navigation/bottom-tabs';
|
import { TransitionPresets } from '@react-navigation/bottom-tabs';
|
||||||
import * as Notifications from 'expo-notifications';
|
import * as Notifications from 'expo-notifications';
|
||||||
import { Tabs } from 'expo-router';
|
import { Tabs, useRouter } from 'expo-router';
|
||||||
import * as SecureStore from 'expo-secure-store';
|
import * as SecureStore from 'expo-secure-store';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -32,12 +32,16 @@ export default function TabLayout() {
|
|||||||
const isMounted = useRef(true);
|
const isMounted = useRef(true);
|
||||||
const [token, setToken] = useState('');
|
const [token, setToken] = useState('');
|
||||||
const [wsStatus, setWsStatus] = useState<WebSocketStatus>('disconnected');
|
const [wsStatus, setWsStatus] = useState<WebSocketStatus>('disconnected');
|
||||||
|
const [notificationPermissionRequested, setNotificationPermissionRequested] = useState(false);
|
||||||
const sendNotification = async (item: PollingData) => {
|
const sendNotification = async (item: PollingData) => {
|
||||||
// 请求通知权限
|
// 只在需要发送通知时才请求权限
|
||||||
const granted = await requestNotificationPermission();
|
if (!notificationPermissionRequested) {
|
||||||
if (!granted) {
|
const granted = await requestNotificationPermission();
|
||||||
console.log('用户拒绝了通知权限');
|
setNotificationPermissionRequested(granted);
|
||||||
return;
|
if (!granted) {
|
||||||
|
console.log('用户拒绝了通知权限');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调度本地通知
|
// 调度本地通知
|
||||||
@ -60,7 +64,18 @@ export default function TabLayout() {
|
|||||||
const notificationListener = Notifications.addNotificationResponseReceivedListener(response => {
|
const notificationListener = Notifications.addNotificationResponseReceivedListener(response => {
|
||||||
const data = response.notification.request.content.data;
|
const data = response.notification.request.content.data;
|
||||||
console.log('通知被点击,数据:', data);
|
console.log('通知被点击,数据:', data);
|
||||||
pollingData?.filter((item) => item.id !== data.id);
|
setPollingData(prev => prev.filter((item) => item.id !== data.id));
|
||||||
|
|
||||||
|
// 根据通知数据导航到指定页面
|
||||||
|
if (data.screen === 'ask') {
|
||||||
|
router.push({
|
||||||
|
pathname: '/ask',
|
||||||
|
params: {
|
||||||
|
sessionId: data.id,
|
||||||
|
extra: data.extra
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 清理监听器
|
// 清理监听器
|
||||||
@ -93,6 +108,7 @@ export default function TabLayout() {
|
|||||||
return () => {
|
return () => {
|
||||||
if (pollingInterval.current) {
|
if (pollingInterval.current) {
|
||||||
clearInterval(pollingInterval.current);
|
clearInterval(pollingInterval.current);
|
||||||
|
pollingInterval.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@ -103,7 +119,9 @@ export default function TabLayout() {
|
|||||||
const response = await fetchApi<PollingData[]>("/notice/push/message", {
|
const response = await fetchApi<PollingData[]>("/notice/push/message", {
|
||||||
method: "POST"
|
method: "POST"
|
||||||
});
|
});
|
||||||
setPollingData((prev) => ([...prev, ...response]));
|
if (response && Array.isArray(response)) {
|
||||||
|
setPollingData((prev) => ([...prev, ...response]));
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取轮询数据时出错:', error);
|
console.error('获取轮询数据时出错:', error);
|
||||||
}
|
}
|
||||||
@ -117,35 +135,50 @@ export default function TabLayout() {
|
|||||||
} else {
|
} else {
|
||||||
tokenValue = (await SecureStore.getItemAsync('token')) || '';
|
tokenValue = (await SecureStore.getItemAsync('token')) || '';
|
||||||
}
|
}
|
||||||
setToken(tokenValue); // 只在获取到新token时更新状态
|
// 只在获取到新token时更新状态
|
||||||
|
if (tokenValue !== token) {
|
||||||
|
setToken(tokenValue);
|
||||||
|
}
|
||||||
return tokenValue;
|
return tokenValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 初始化时获取一次token
|
||||||
|
useEffect(() => {
|
||||||
|
getAuthToken();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkAuthStatus = async () => {
|
const checkAuthStatus = async () => {
|
||||||
try {
|
try {
|
||||||
if (token) {
|
if (token) {
|
||||||
// 启动轮询
|
// 启动轮询
|
||||||
startPolling(5000);
|
const cleanup = startPolling(5000);
|
||||||
|
// 将清理函数保存到ref中,以便在组件卸载时调用
|
||||||
|
return cleanup;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取推送消息出错:', error);
|
console.error('获取推送消息出错:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
checkAuthStatus();
|
|
||||||
|
const cleanupPolling = checkAuthStatus();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// 清理函数
|
// 清理函数
|
||||||
if (pollingInterval.current) {
|
if (pollingInterval.current) {
|
||||||
clearInterval(pollingInterval.current);
|
clearInterval(pollingInterval.current);
|
||||||
|
pollingInterval.current = null;
|
||||||
|
}
|
||||||
|
if (typeof cleanupPolling === 'function') {
|
||||||
|
cleanupPolling();
|
||||||
}
|
}
|
||||||
isMounted.current = false;
|
isMounted.current = false;
|
||||||
};
|
};
|
||||||
}, [token]);
|
}, [token, startPolling]);
|
||||||
|
|
||||||
// 本地推送
|
// 本地推送
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pollingData?.map((item) => {
|
pollingData?.forEach((item) => {
|
||||||
sendNotification(item)
|
sendNotification(item)
|
||||||
})
|
})
|
||||||
}, [pollingData])
|
}, [pollingData])
|
||||||
@ -156,10 +189,11 @@ export default function TabLayout() {
|
|||||||
if (token) {
|
if (token) {
|
||||||
if (tokenInterval.current) {
|
if (tokenInterval.current) {
|
||||||
clearInterval(tokenInterval.current);
|
clearInterval(tokenInterval.current);
|
||||||
|
tokenInterval.current = null;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!tokenInterval.current) return;
|
|
||||||
// 设置轮询
|
// 设置轮询
|
||||||
tokenInterval.current = setInterval(async () => {
|
tokenInterval.current = setInterval(async () => {
|
||||||
if (isMounted.current) {
|
if (isMounted.current) {
|
||||||
@ -167,6 +201,7 @@ export default function TabLayout() {
|
|||||||
// 如果获取到token,清除定时器
|
// 如果获取到token,清除定时器
|
||||||
if (currentToken && tokenInterval.current) {
|
if (currentToken && tokenInterval.current) {
|
||||||
clearInterval(tokenInterval.current);
|
clearInterval(tokenInterval.current);
|
||||||
|
tokenInterval.current = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
@ -175,6 +210,7 @@ export default function TabLayout() {
|
|||||||
return () => {
|
return () => {
|
||||||
if (tokenInterval.current) {
|
if (tokenInterval.current) {
|
||||||
clearInterval(tokenInterval.current);
|
clearInterval(tokenInterval.current);
|
||||||
|
tokenInterval.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [token]); // 添加token作为依赖
|
}, [token]); // 添加token作为依赖
|
||||||
|
|||||||
@ -37,9 +37,10 @@ export default function AskScreen() {
|
|||||||
const fadeAnimChat = useRef(new Animated.Value(0)).current;
|
const fadeAnimChat = useRef(new Animated.Value(0)).current;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { sessionId, newSession } = useLocalSearchParams<{
|
const { sessionId, newSession, extra } = useLocalSearchParams<{
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
newSession: string;
|
newSession: string;
|
||||||
|
extra: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 创建一个可复用的滚动函数
|
// 创建一个可复用的滚动函数
|
||||||
|
|||||||
@ -22,7 +22,6 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [rememberMe, setRememberMe] = useState(false);
|
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
@ -44,12 +43,18 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
}, true, false);
|
}, true, false);
|
||||||
login({ ...res, email: res?.account }, res.access_token || '');
|
|
||||||
const userInfo = await fetchApi<User>("/iam/user-info");
|
// 确保用户数据和令牌存在
|
||||||
if (userInfo?.nickname) {
|
if (res && res.access_token) {
|
||||||
router.replace('/ask');
|
login({ ...res, email: res?.account || email }, res.access_token);
|
||||||
|
const userInfo = await fetchApi<User>("/iam/user-info");
|
||||||
|
if (userInfo?.nickname) {
|
||||||
|
router.replace('/ask');
|
||||||
|
} else {
|
||||||
|
router.replace('/user-message');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
router.replace('/user-message');
|
throw new Error(t('auth.login.loginError', { ns: 'login' }));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : t('auth.login.loginError', { ns: 'login' });
|
const errorMessage = error instanceof Error ? error.message : t('auth.login.loginError', { ns: 'login' });
|
||||||
|
|||||||
@ -31,9 +31,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
if (Platform.OS === 'web') {
|
if (Platform.OS === 'web') {
|
||||||
token = localStorage.getItem('token') || "";
|
token = localStorage.getItem('token') || "";
|
||||||
} else {
|
} else {
|
||||||
await SecureStore.getItemAsync('token').then((token) => {
|
const storedToken = await SecureStore.getItemAsync('token');
|
||||||
token = token || "";
|
token = storedToken || "";
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if (token) {
|
if (token) {
|
||||||
// 验证当前 token 是否有效
|
// 验证当前 token 是否有效
|
||||||
@ -61,7 +60,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
// 刷新 token 失败,才进行登出操作
|
// 刷新 token 失败,才进行登出操作
|
||||||
// console.error("Token refresh failed, logging out", refreshError);
|
// console.error("Token refresh failed, logging out", refreshError);
|
||||||
logout();
|
// 只有在确实需要登出时才调用logout()
|
||||||
|
// 如果是首次启动应用,没有token是正常的,不需要登出
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,9 +29,8 @@ export const API_ENDPOINT = Constants.expoConfig?.extra?.API_ENDPOINT || "http:/
|
|||||||
|
|
||||||
|
|
||||||
// 更新 access_token 的逻辑 - 用于React组件中
|
// 更新 access_token 的逻辑 - 用于React组件中
|
||||||
export const useAuthToken = async<T>(message: string | null) => {
|
export const useAuthToken = async<T>(message: string | null, login: (user: User, jwt: string) => void) => {
|
||||||
try {
|
try {
|
||||||
const { login } = useAuth();
|
|
||||||
const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`);
|
const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`);
|
||||||
const apiResponse: ApiResponse<T> = await response.json();
|
const apiResponse: ApiResponse<T> = await response.json();
|
||||||
|
|
||||||
@ -62,13 +61,32 @@ export const refreshAuthToken = async<T>(message: string | null): Promise<User>
|
|||||||
let cookie = "";
|
let cookie = "";
|
||||||
let userId = "";
|
let userId = "";
|
||||||
if (Platform.OS === 'web') {
|
if (Platform.OS === 'web') {
|
||||||
cookie = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || "")?.refresh_token || "" : "";
|
const userStr = localStorage.getItem('user');
|
||||||
userId = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || "")?.user_id || "" : "";
|
if (userStr) {
|
||||||
|
try {
|
||||||
|
const userObj = JSON.parse(userStr);
|
||||||
|
cookie = userObj?.refresh_token || "";
|
||||||
|
userId = userObj?.user_id || "";
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse user data from localStorage", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await SecureStore.getItemAsync('user').then((user: string | null) => {
|
const userStr = await SecureStore.getItemAsync('user');
|
||||||
cookie = JSON.parse(user || "")?.refresh_token || "";
|
if (userStr) {
|
||||||
userId = JSON.parse(user || "")?.user_id || "";
|
try {
|
||||||
})
|
const userObj = JSON.parse(userStr);
|
||||||
|
cookie = userObj?.refresh_token || "";
|
||||||
|
userId = userObj?.user_id || "";
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse user data from SecureStore", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有必要的数据,抛出错误
|
||||||
|
if (!cookie || !userId) {
|
||||||
|
throw new Error("Missing refresh token or user ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 退出刷新会重新填充数据
|
// 退出刷新会重新填充数据
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user