191 lines
6.3 KiB
TypeScript
191 lines
6.3 KiB
TypeScript
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 = Constants.expoConfig?.extra?.API_ENDPOINT || "http://192.168.31.16:31646/api";
|
||
|
||
// 更新 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> => {
|
||
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);
|
||
}
|
||
}; |