You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/oaer/lib/api/webapi.ts

200 lines
5.7 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2024-10-28 17:46:56
// Distributed under terms of the MIT license.
//
import axios, { AxiosError, type AxiosInstance, type AxiosResponse } from 'axios';
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
export const token = {
value: '',
update: () => {
console.warn('token updater not set')
return new Promise<string>((resolve) => { resolve(token.value) })
},
set_updator: (fn: () => Promise<string>) => {
let locked = false
token.update = () => {
if (locked) {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, 1000)
}).then(() => {
return token.update()
})
}
locked = true
return new Promise<string>((resolve, reject) => {
if (token.value) {
locked = false
resolve(token.value)
return
}
fn().then((e) => {
token.value = e
resolve(e)
}).catch(() => {
reject()
}).finally(() => { locked = false })
})
}
}
}
// 请求拦截
const beforeRequest = (config: any) => {
config.retryTimes = config.retryTimes || 3
// NOTE 添加自定义头部
token.value && (config.headers.Authorization = `Bearer ${token.value}`)
return config
}
// 响应拦截器
const responseSuccess = (client: AxiosInstance) => {
return (response: AxiosResponse) => {
// eslint-disable-next-line yoda
// 这里没有必要进行判断axios 内部已经判断
// const isOk = 200 <= response.status && response.status < 300
let data = response.data
if (typeof data === 'object') {
if (data.code !== 0) {
return responseFailed(client)({ response } as any)
}
data = data.data
}
return Promise.resolve(data)
}
}
const responseFailed = (client: AxiosInstance) => {
return (error: AxiosError) => {
const { response } = error
const config = response?.config
const data = response?.data || {} as any
if (!window.navigator.onLine) {
alert('没有网络')
return Promise.reject(new Error('请检查网络连接'))
}
// @ts-ignore
let needRetry = config?.needRetry !== false
if (response?.status == 404) {
needRetry = false
} else if (response?.status == 401 && needRetry) {
needRetry = false
// AuthNotFound = New(40100, "auth not found")
// AuthExpired = New(40102, "auth expired")
if (data.code === 40102 || data.code === 40100) {
token.value = ''
return token.update().then(() => {
return requestRetry(client)(200, response!)
})
}
} else if (response?.status == 500) {
needRetry = false
}
if (!needRetry) {
return Promise.reject(data || response)
};
return requestRetry(client)(1000, response!)
}
}
const requestRetry = (client: AxiosInstance) => {
return (delay = 0, response: AxiosResponse) => {
const config = response?.config
// @ts-ignore
const { __retryCount = 0, retryDelay = 1000, retryTimes } = config;
// 在请求对象上设置重试次数
// @ts-ignore
config.__retryCount = __retryCount + 1;
// 判断是否超过了重试次数
if (__retryCount >= retryTimes) {
return Promise.reject(response?.data || response?.headers.error)
}
if (delay <= 0) {
return client.request(config as any)
}
// 延时处理
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, delay)
}).then(() => {
return client.request(config as any)
})
}
}
const create_client = (client: AxiosInstance) => {
client.interceptors.request.use(beforeRequest)
client.interceptors.response.use(responseSuccess(client), responseFailed(client))
return client
}
interface data {
json?: any
query?: any
form?: any
header?: any
config?: Object
}
function transData(d: data) {
let opts = { params: d.query, data: {}, headers: {} as any }
if (d.form) {
opts.data = d.form
opts.headers['content-type'] = 'application/x-www-form-urlencoded'
}
if (d.json) {
opts.data = d.json
opts.headers['content-type'] = 'application/json'
}
if (d.header) {
opts.headers = Object.assign(opts.headers, d.header)
}
if (d.config) {
opts = Object.assign(opts, d.config)
}
return opts
}
export const webapi = {
client: create_client(axios.create({
withCredentials: true,
baseURL: "/api/",
headers: {
'content-type': 'application/json;charset=UTF-8',
},
})),
Get<T>(url: string, req: data): Promise<T> {
return webapi.client.request<T, any>(Object.assign({ method: 'get', url: url }, transData(req)))
},
Head<T>(url: string, req: data): Promise<T> {
return webapi.client.request<T, any>(Object.assign({ method: 'head', url: url }, transData(req)))
},
Delete<T>(url: string, req: data): Promise<T> {
return webapi.client.request<T, any>(Object.assign({ method: 'delete', url: url }, transData(req)))
},
Post<T>(url: string, req: data): Promise<T> {
return webapi.client.request<T, any>(Object.assign({ method: 'post', url: url }, transData(req)))
},
Put<T>(url: string, req: data): Promise<T> {
return webapi.client.request<T, any>(Object.assign({ method: 'put', url: url }, transData(req)))
},
Patch<T>(url: string, req: data): Promise<T> {
return webapi.client.request<T, any>(Object.assign({ method: 'patch', url: url }, transData(req)))
},
}
export default webapi