memowake-front/lib/server-api-util.ts
Junhui Chen 665ccc6132
All checks were successful
Dev Deploy / Explore-Gitea-Actions (push) Successful in 31s
enhance: ws util健壮性优化
2025-08-09 15:17:22 +08:00

193 lines
6.4 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 { setCredentials } from '@/features/auth/authSlice';
import Constants from 'expo-constants';
import * as SecureStore from 'expo-secure-store';
import { Platform } from 'react-native';
import Toast from 'react-native-toast-message';
import { useAuth } from '../contexts/auth-context';
import { store } from '../store';
import { User } from '../types/user';
// 定义错误码常量
const ERROR_CODES = {
UNAUTHORIZED: 1004010001,
} as const;
export interface ApiResponse<T> {
code: number;
data: T | null;
message: string | null;
}
export interface PagedResult<T> {
items: T[];
has_more: boolean;
}
// 获取.env文件中的变量
export const API_ENDPOINT = process.env.EXPO_PUBLIC_API_ENDPOINT || Constants.expoConfig?.extra?.API_ENDPOINT;
// 更新 access_token 的逻辑 - 用于React组件中
export const useAuthToken = async<T>(message: string | null) => {
try {
const { login } = useAuth();
const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`);
const apiResponse: ApiResponse<T> = await response.json();
// 如果接口报错,页面弹出来错误信息
if (apiResponse.code != 0) {
if (Platform.OS === 'web') {
Toast.show({
type: 'error',
text1: apiResponse.message || 'Request failed'
});
}
throw new Error(message || 'Unknown error');
} else {
const userData = apiResponse.data as User;
// 接口未报错通过auth-context文件的login函数来更新access token
login(userData, userData.access_token || "");
}
return apiResponse;
} catch (error) {
console.error(`Error fetching data from /iam/access-token-refresh:`, error);
throw error;
}
}
// 使用Redux存储token的刷新token函数
export const refreshAuthToken = async<T>(message: string | null): Promise<User> => {
try {
let cookie = "";
let userId = "";
if (Platform.OS === 'web') {
cookie = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || "")?.refresh_token || "" : "";
userId = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || "")?.user_id || "" : "";
} else {
await SecureStore.getItemAsync('user').then((user: string | null) => {
cookie = JSON.parse(user || "")?.refresh_token || "";
userId = JSON.parse(user || "")?.user_id || "";
})
}
// 退出刷新会重新填充数据
let response;
response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`, {
method: "POST",
body: JSON.stringify({
"refresh_token": cookie,
"user_id": userId
}),
headers: {
'Content-Type': 'application/json',
}
});
const apiResponse: ApiResponse<T> = await response.json();
if (apiResponse.code != 0) {
throw new Error(message || 'Unknown error');
}
const userData = apiResponse.data as User;
if (userData && userData.access_token) {
// 使用Redux更新token
store.dispatch(setCredentials({
user: userData,
token: userData.access_token
}));
if (Platform.OS === 'web') {
localStorage.setItem('user', JSON.stringify(userData));
localStorage.setItem('token', userData.access_token);
} else {
SecureStore.setItemAsync('user', JSON.stringify(userData));
SecureStore.setItemAsync('token', userData.access_token);
}
}
return userData;
} catch (error) {
// console.error(`Error refreshing token:`, error);
throw error;
}
}
// 处理 API 错误
const handleApiError = (error: unknown, needToast = true, defaultMessage = 'Unknown error') => {
const message = error instanceof Error ? error.message : defaultMessage;
if (needToast) {
// console.log(message);
}
throw new Error(message);
};
// 处理必须携带 token 的接口
export const fetchApi = async <T>(
path: string,
options: RequestInit = {},
needToast = true,
needToken = true,
): Promise<T> => {
// console.log("API_ENDPOINT", Constants.expoConfig?.extra?.API_ENDPOINT);
const makeRequest = async (isRetry = false): Promise<ApiResponse<T>> => {
try {
let token = "";
if (Platform.OS === 'web') {
token = localStorage.getItem('token') || "";
} else {
token = await SecureStore.getItemAsync('token') || "";
}
const headers = new Headers(options.headers);
// 添加必要的 headers
if (options.method !== 'GET' && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
if (token != null && needToken) {
headers.set('Authorization', `Bearer ${token}`);
}
const url = `${API_ENDPOINT}/v1${path}`;
const response = await fetch(url, {
...options,
headers,
});
const apiResponse: ApiResponse<T> = await response.json();
// 处理未授权错误(需要刷新 token
if (apiResponse.code === ERROR_CODES.UNAUTHORIZED && !isRetry) {
await refreshAuthToken(apiResponse.message);
return makeRequest(true); // 重试请求
}
// 处理其他错误
if (apiResponse.code !== 0) {
// 如果是web端则显示提示
if (Platform.OS === 'web') {
Toast.show({
type: 'error',
text1: apiResponse.message || 'Request failed'
});
}
throw new Error(apiResponse.message || 'Request failed');
}
return apiResponse;
} catch (error) {
if (error instanceof Error) {
throw error;
}
throw new Error('An unknown error occurred');
}
};
try {
const response = await makeRequest();
return response.data || ({} as T);
} catch (error) {
return handleApiError(error, needToast);
}
};