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

//
// Copyright (C) 2024 veypi <i@veypi.com>
4 weeks ago
// 2024-10-28 17:46:56
// Distributed under terms of the MIT license.
//
4 weeks ago
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: () => {
4 weeks ago
console.warn('token updater not set')
return new Promise<string>((resolve) => { resolve(token.value) })
},
4 weeks ago
set_updator: (fn: () => Promise<string>) => {
let locked = false
token.update = () => {
4 weeks ago
if (locked) {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, 1000)
}).then(() => {
return token.update()
})
}
locked = true
return new Promise<string>((resolve, reject) => {
4 weeks ago
if (token.value) {
locked = false
resolve(token.value)
return
}
fn().then((e) => {
4 weeks ago
token.value = e
resolve(e)
}).catch(() => {
reject()
4 weeks ago
}).finally(() => { locked = false })
})
}
}
}
// 请求拦截
const beforeRequest = (config: any) => {
4 weeks ago
config.retryTimes = config.retryTimes || 3
// NOTE 添加自定义头部
token.value && (config.headers.Authorization = `Bearer ${token.value}`)
return config
}
// 响应拦截器
4 weeks ago
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
}
4 weeks ago
return Promise.resolve(data)
}
}
4 weeks ago
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('请检查网络连接'))
}
4 weeks ago
// @ts-ignore
let needRetry = config?.needRetry !== false
4 weeks ago
if (response?.status == 404) {
needRetry = false
4 weeks ago
} else if (response?.status == 401 && needRetry) {
4 weeks ago
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(() => {
4 weeks ago
return requestRetry(client)(200, response!)
4 weeks ago
})
}
} else if (response?.status == 500) {
needRetry = false
}
4 weeks ago
if (!needRetry) {
return Promise.reject(data || response)
};
return requestRetry(client)(1000, response!)
}
4 weeks ago
}
4 weeks ago
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)
})
4 weeks ago
}
}
4 weeks ago
4 weeks ago
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
4 weeks ago
config?: Object
}
4 weeks ago
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)
}
4 weeks ago
if (d.config) {
opts = Object.assign(opts, d.config)
}
return opts
}
export const webapi = {
4 weeks ago
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> {
4 weeks ago
return webapi.client.request<T, any>(Object.assign({ method: 'get', url: url }, transData(req)))
},
Head<T>(url: string, req: data): Promise<T> {
4 weeks ago
return webapi.client.request<T, any>(Object.assign({ method: 'head', url: url }, transData(req)))
},
Delete<T>(url: string, req: data): Promise<T> {
4 weeks ago
return webapi.client.request<T, any>(Object.assign({ method: 'delete', url: url }, transData(req)))
},
Post<T>(url: string, req: data): Promise<T> {
4 weeks ago
return webapi.client.request<T, any>(Object.assign({ method: 'post', url: url }, transData(req)))
},
Put<T>(url: string, req: data): Promise<T> {
4 weeks ago
return webapi.client.request<T, any>(Object.assign({ method: 'put', url: url }, transData(req)))
},
Patch<T>(url: string, req: data): Promise<T> {
4 weeks ago
return webapi.client.request<T, any>(Object.assign({ method: 'patch', url: url }, transData(req)))
},
}
export default webapi