forked from QuantumNous/new-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhttp-client.ts
More file actions
150 lines (132 loc) · 4.45 KB
/
Copy pathhttp-client.ts
File metadata and controls
150 lines (132 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import axios, { type AxiosRequestConfig } from 'axios'
import { t } from 'i18next'
import { toast } from 'sonner'
import {
applyAuthRotation,
clearAuthentication,
refreshAuthentication,
} from '@/lib/auth-session'
import { getServerErrorMessageKey } from '@/lib/server-error-message'
import { useAuthStore } from '@/stores/auth-store'
declare module 'axios' {
export interface AxiosRequestConfig {
skipBusinessError?: boolean
skipErrorHandler?: boolean
disableDuplicate?: boolean
skipAuthRefresh?: boolean
authRetry?: boolean
acceptAuthRotation?: boolean
}
}
export type ApiRequestConfig = AxiosRequestConfig
export const api = axios.create({
baseURL: '',
withCredentials: true,
headers: {
'Cache-Control': 'no-store',
},
})
const inFlightGet = new Map<string, Promise<unknown>>()
const originalGet = api.get.bind(api)
api.get = ((url: string, config: ApiRequestConfig = {}) => {
if (config.disableDuplicate) return originalGet(url, config)
const params = config.params ? JSON.stringify(config.params) : '{}'
const sessionSID = useAuthStore.getState().auth.session?.sid || 'anonymous'
const key = `${sessionSID}:${url}?${params}`
const existingRequest = inFlightGet.get(key)
if (existingRequest) return existingRequest
const request = originalGet(url, config).finally(() => {
inFlightGet.delete(key)
})
inFlightGet.set(key, request)
return request
}) as typeof api.get
function redirectToSignIn(): void {
if (
typeof window !== 'undefined' &&
window.location.pathname !== '/sign-in'
) {
window.location.replace('/sign-in')
}
}
api.interceptors.response.use(
(response) => {
if (response.config.acceptAuthRotation && response.data?.success === true) {
applyAuthRotation(response.data.data)
}
if (
!response.config.skipBusinessError &&
typeof response.data?.success === 'boolean' &&
!response.data.success
) {
const messageKey = getServerErrorMessageKey(response.data)
toast.error(
messageKey
? t(messageKey)
: response.data.message || t('Request failed')
)
}
return response
},
async (error) => {
const config = error?.config as ApiRequestConfig | undefined
const skipErrorHandler = config?.skipErrorHandler
const status = error?.response?.status
if (status === 401) {
if (config && !config.skipAuthRefresh && !config.authRetry) {
config.authRetry = true
const outcome = await refreshAuthentication()
if (outcome.kind === 'authenticated') {
const token = useAuthStore.getState().auth.accessToken
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
}
}
return api.request(config)
}
if (outcome.kind === 'anonymous' || outcome.kind === 'out_of_sync') {
if (!skipErrorHandler) toast.error(t('Session expired!'))
redirectToSignIn()
}
} else if (config?.authRetry) {
clearAuthentication(false)
if (!skipErrorHandler) toast.error(t('Session expired!'))
redirectToSignIn()
} else if (!skipErrorHandler) {
toast.error(t('Session expired!'))
}
} else if (!skipErrorHandler) {
const messageKey = getServerErrorMessageKey(error)
const message = messageKey
? t(messageKey)
: error?.response?.data?.message ||
error?.message ||
t('Request failed')
toast.error(message)
}
throw error
}
)
api.interceptors.request.use((config) => {
const accessToken = useAuthStore.getState().auth.accessToken
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`
}
return config
})