@codingfactory/socialkit-vue 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composables/useAuth.d.ts +27 -0
- package/dist/composables/useAuth.d.ts.map +1 -0
- package/dist/composables/useAuth.js +137 -0
- package/dist/composables/useAuth.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/terminology/index.d.ts +11 -0
- package/dist/plugins/terminology/index.d.ts.map +1 -0
- package/dist/plugins/terminology/index.js +91 -0
- package/dist/plugins/terminology/index.js.map +1 -0
- package/dist/plugins/terminology/terms.d.ts +15 -0
- package/dist/plugins/terminology/terms.d.ts.map +1 -0
- package/dist/plugins/terminology/terms.js +72 -0
- package/dist/plugins/terminology/terms.js.map +1 -0
- package/dist/plugins/terminology/types.d.ts +32 -0
- package/dist/plugins/terminology/types.d.ts.map +1 -0
- package/dist/plugins/terminology/types.js +2 -0
- package/dist/plugins/terminology/types.js.map +1 -0
- package/dist/services/api.d.ts +50 -0
- package/dist/services/api.d.ts.map +1 -0
- package/dist/services/api.js +305 -0
- package/dist/services/api.js.map +1 -0
- package/dist/services/auth.d.ts +127 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +562 -0
- package/dist/services/auth.js.map +1 -0
- package/dist/stores/auth.d.ts +174 -0
- package/dist/stores/auth.d.ts.map +1 -0
- package/dist/stores/auth.js +262 -0
- package/dist/stores/auth.js.map +1 -0
- package/dist/types/api.d.ts +52 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +7 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/user.d.ts +42 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +45 -0
- package/dist/types/user.js.map +1 -0
- package/dist/utils/tokenStorage.d.ts +41 -0
- package/dist/utils/tokenStorage.d.ts.map +1 -0
- package/dist/utils/tokenStorage.js +300 -0
- package/dist/utils/tokenStorage.js.map +1 -0
- package/package.json +40 -0
- package/src/composables/useAuth.ts +164 -0
- package/src/index.ts +118 -0
- package/src/plugins/terminology/index.ts +114 -0
- package/src/plugins/terminology/terms.ts +104 -0
- package/src/plugins/terminology/types.ts +28 -0
- package/src/services/api.ts +472 -0
- package/src/services/auth.ts +874 -0
- package/src/stores/auth.ts +400 -0
- package/src/types/api.ts +56 -0
- package/src/types/user.ts +94 -0
- package/src/utils/tokenStorage.ts +394 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurable API service for SocialKit-powered frontends.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios from 'axios'
|
|
6
|
+
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
|
7
|
+
import type { ApiResponse, RequestConfig } from '../types/api.js'
|
|
8
|
+
import type { TokenStorage } from '../utils/tokenStorage.js'
|
|
9
|
+
|
|
10
|
+
/** Configuration for creating an API service instance. */
|
|
11
|
+
export interface ApiServiceConfig {
|
|
12
|
+
/** Base URL for API requests (e.g. '/api' or 'https://api.example.com/api'). */
|
|
13
|
+
baseURL: string
|
|
14
|
+
/** Token storage instance for auth headers and refresh logic. */
|
|
15
|
+
tokenStorage: TokenStorage
|
|
16
|
+
/** Optional existing Axios client to configure instead of creating a new one. */
|
|
17
|
+
client?: AxiosInstance
|
|
18
|
+
/** Request timeout in milliseconds (default: 30000). */
|
|
19
|
+
timeout?: number
|
|
20
|
+
/** Paths that are considered auth pages and won't be redirected to (default: ['/login', '/register']). */
|
|
21
|
+
authPagePaths?: string[]
|
|
22
|
+
/** Path to redirect to when auth fails (default: '/login'). */
|
|
23
|
+
loginPath?: string
|
|
24
|
+
/** Delay in ms before redirecting on auth failure (default: 1500). */
|
|
25
|
+
authFailureRedirectDelayMs?: number
|
|
26
|
+
/** Optional error handler called on every response error. */
|
|
27
|
+
onResponseError?: (error: unknown) => void
|
|
28
|
+
/** Optional callback invoked just before local auth state is cleared. */
|
|
29
|
+
onAuthInvalidated?: () => void
|
|
30
|
+
/** Override the redirect target used when auth teardown occurs. */
|
|
31
|
+
buildLoginRedirectUrl?: () => string
|
|
32
|
+
/** Max retries for transient 502/503/504 idempotent requests (default: 2). */
|
|
33
|
+
maxServerRetries?: number
|
|
34
|
+
/** Base delay in ms for transient server retries (default: 500). */
|
|
35
|
+
serverRetryBaseDelayMs?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface RetriableRequestConfig extends AxiosRequestConfig {
|
|
39
|
+
_retry?: boolean
|
|
40
|
+
_policyRetry?: boolean
|
|
41
|
+
_serverRetryCount?: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface MissingPolicy {
|
|
45
|
+
policy_id: string
|
|
46
|
+
version: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
50
|
+
return typeof value === 'object' && value !== null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isTransientServerError(status: number | undefined): boolean {
|
|
54
|
+
return status === 502 || status === 503 || status === 504
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isIdempotentMethod(method: string | undefined): boolean {
|
|
58
|
+
const normalizedMethod = (method ?? 'get').toLowerCase()
|
|
59
|
+
return normalizedMethod === 'get' || normalizedMethod === 'head' || normalizedMethod === 'options'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function delay(ms: number): Promise<void> {
|
|
63
|
+
return new Promise((resolve) => { window.setTimeout(resolve, ms) })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseMissingPolicies(payload: unknown): MissingPolicy[] {
|
|
67
|
+
if (!isRecord(payload)) {
|
|
68
|
+
return []
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const errors = payload.errors
|
|
72
|
+
if (!isRecord(errors)) {
|
|
73
|
+
return []
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const missing = errors.missing
|
|
77
|
+
if (!Array.isArray(missing)) {
|
|
78
|
+
return []
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const parsed: MissingPolicy[] = []
|
|
82
|
+
|
|
83
|
+
for (const item of missing) {
|
|
84
|
+
if (!isRecord(item)) {
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const policyId = item.policy_id
|
|
89
|
+
const version = item.version
|
|
90
|
+
if (typeof policyId !== 'string') {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const parsedVersion = typeof version === 'number'
|
|
95
|
+
? version
|
|
96
|
+
: typeof version === 'string'
|
|
97
|
+
? Number.parseInt(version, 10)
|
|
98
|
+
: Number.NaN
|
|
99
|
+
|
|
100
|
+
if (!Number.isInteger(parsedVersion)) {
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
parsed.push({ policy_id: policyId, version: parsedVersion })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return parsed
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isPoliciesPendingErrorPayload(payload: unknown): boolean {
|
|
111
|
+
return isRecord(payload) && payload.code === 'POLICIES_PENDING'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function dedupeMissingPolicies(policies: MissingPolicy[]): MissingPolicy[] {
|
|
115
|
+
const deduped = new Map<string, MissingPolicy>()
|
|
116
|
+
|
|
117
|
+
for (const policy of policies) {
|
|
118
|
+
const existing = deduped.get(policy.policy_id)
|
|
119
|
+
if (!existing || policy.version > existing.version) {
|
|
120
|
+
deduped.set(policy.policy_id, policy)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return [...deduped.values()]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function cloneRequestConfig(config: RetriableRequestConfig): RetriableRequestConfig {
|
|
128
|
+
return {
|
|
129
|
+
...config,
|
|
130
|
+
headers: { ...(config.headers as Record<string, string> | undefined) }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function withAuthorizationHeader(
|
|
135
|
+
config: RetriableRequestConfig,
|
|
136
|
+
token: string
|
|
137
|
+
): RetriableRequestConfig {
|
|
138
|
+
return {
|
|
139
|
+
...config,
|
|
140
|
+
headers: {
|
|
141
|
+
...(config.headers as Record<string, string> | undefined),
|
|
142
|
+
Authorization: `Bearer ${token}`
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createConfiguredClient(
|
|
148
|
+
baseURL: string,
|
|
149
|
+
timeout: number,
|
|
150
|
+
client?: AxiosInstance
|
|
151
|
+
): AxiosInstance {
|
|
152
|
+
if (client) {
|
|
153
|
+
client.defaults.baseURL = baseURL
|
|
154
|
+
client.defaults.timeout = timeout
|
|
155
|
+
client.defaults.headers.common.Accept = 'application/json'
|
|
156
|
+
client.defaults.headers.common['Content-Type'] = 'application/json'
|
|
157
|
+
client.defaults.withCredentials = false
|
|
158
|
+
return client
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return axios.create({
|
|
162
|
+
baseURL,
|
|
163
|
+
timeout,
|
|
164
|
+
headers: {
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
Accept: 'application/json'
|
|
167
|
+
},
|
|
168
|
+
withCredentials: false
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Public API service interface. */
|
|
173
|
+
export interface ApiService {
|
|
174
|
+
get<T = unknown>(url: string, config?: RequestConfig): Promise<ApiResponse<T>>
|
|
175
|
+
post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<ApiResponse<T>>
|
|
176
|
+
put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<ApiResponse<T>>
|
|
177
|
+
patch<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<ApiResponse<T>>
|
|
178
|
+
delete<T = unknown>(url: string, config?: RequestConfig): Promise<ApiResponse<T>>
|
|
179
|
+
postForm<T = unknown>(url: string, form: FormData, config?: RequestConfig): Promise<T>
|
|
180
|
+
postFormRaw<T = unknown>(url: string, form: FormData, config?: RequestConfig): Promise<AxiosResponse<T>>
|
|
181
|
+
markAuthRecoveryHandled(): void
|
|
182
|
+
hasPendingAuthRequest(): boolean
|
|
183
|
+
clearPendingAuthRequest(): void
|
|
184
|
+
replayPendingAuthRequest(token: string): Promise<void>
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Create a configured API service instance. */
|
|
188
|
+
export function createApiService(config: ApiServiceConfig): ApiService {
|
|
189
|
+
const {
|
|
190
|
+
baseURL,
|
|
191
|
+
tokenStorage,
|
|
192
|
+
client,
|
|
193
|
+
timeout = 30000,
|
|
194
|
+
authPagePaths = ['/login', '/register'],
|
|
195
|
+
loginPath = '/login',
|
|
196
|
+
authFailureRedirectDelayMs = 1500,
|
|
197
|
+
onResponseError,
|
|
198
|
+
onAuthInvalidated,
|
|
199
|
+
buildLoginRedirectUrl,
|
|
200
|
+
maxServerRetries = 2,
|
|
201
|
+
serverRetryBaseDelayMs = 500
|
|
202
|
+
} = config
|
|
203
|
+
|
|
204
|
+
const clientInstance = createConfiguredClient(baseURL, timeout, client)
|
|
205
|
+
|
|
206
|
+
let pendingAuthRequests: RetriableRequestConfig[] = []
|
|
207
|
+
let authFailureRedirectTimeoutId: number | null = null
|
|
208
|
+
let authFailureQueuedToken: string | null = null
|
|
209
|
+
let policyAcceptancePromise: Promise<boolean> | null = null
|
|
210
|
+
|
|
211
|
+
function isPolicyAcceptRequest(url: unknown): boolean {
|
|
212
|
+
return typeof url === 'string' && url.includes('/v1/policy/accept')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function acceptMissingPolicies(missingPolicies: MissingPolicy[]): Promise<boolean> {
|
|
216
|
+
const policiesToAccept = dedupeMissingPolicies(missingPolicies)
|
|
217
|
+
if (policiesToAccept.length === 0) {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (policyAcceptancePromise) {
|
|
222
|
+
return policyAcceptancePromise
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
policyAcceptancePromise = (async () => {
|
|
226
|
+
try {
|
|
227
|
+
for (const policy of policiesToAccept) {
|
|
228
|
+
await clientInstance.post('/v1/policy/accept', {
|
|
229
|
+
policy_id: policy.policy_id,
|
|
230
|
+
version: policy.version
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return true
|
|
235
|
+
} catch {
|
|
236
|
+
return false
|
|
237
|
+
} finally {
|
|
238
|
+
policyAcceptancePromise = null
|
|
239
|
+
}
|
|
240
|
+
})()
|
|
241
|
+
|
|
242
|
+
return policyAcceptancePromise
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function capturePendingAuthRequest(reqConfig: RetriableRequestConfig): void {
|
|
246
|
+
pendingAuthRequests.push(cloneRequestConfig(reqConfig))
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function cancelQueuedAuthFailureRedirect(): void {
|
|
250
|
+
if (authFailureRedirectTimeoutId === null) {
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
window.clearTimeout(authFailureRedirectTimeoutId)
|
|
255
|
+
authFailureRedirectTimeoutId = null
|
|
256
|
+
authFailureQueuedToken = null
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function queueAuthFailureRedirect(): void {
|
|
260
|
+
if (authFailureRedirectTimeoutId !== null) {
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
authFailureQueuedToken = tokenStorage.getToken()
|
|
265
|
+
|
|
266
|
+
authFailureRedirectTimeoutId = window.setTimeout(() => {
|
|
267
|
+
authFailureRedirectTimeoutId = null
|
|
268
|
+
const queuedToken = authFailureQueuedToken
|
|
269
|
+
authFailureQueuedToken = null
|
|
270
|
+
const currentToken = tokenStorage.getToken()
|
|
271
|
+
|
|
272
|
+
if (
|
|
273
|
+
queuedToken !== null &&
|
|
274
|
+
currentToken !== null &&
|
|
275
|
+
currentToken !== queuedToken
|
|
276
|
+
) {
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
onAuthInvalidated?.()
|
|
281
|
+
tokenStorage.removeToken()
|
|
282
|
+
|
|
283
|
+
const currentPath = window.location.pathname
|
|
284
|
+
if (authPagePaths.includes(currentPath)) {
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const redirectUrl = buildLoginRedirectUrl?.() ?? loginPath
|
|
289
|
+
window.location.assign(redirectUrl)
|
|
290
|
+
}, authFailureRedirectDelayMs)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
clientInstance.interceptors.request.use(
|
|
294
|
+
(reqConfig) => {
|
|
295
|
+
const token = tokenStorage.getToken()
|
|
296
|
+
if (token) {
|
|
297
|
+
reqConfig.headers.Authorization = `Bearer ${token}`
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const isFormData = typeof FormData !== 'undefined' && reqConfig.data instanceof FormData
|
|
301
|
+
if (isFormData) {
|
|
302
|
+
delete reqConfig.headers['Content-Type']
|
|
303
|
+
delete reqConfig.headers['content-type']
|
|
304
|
+
reqConfig.transformRequest = [(data) => data]
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return reqConfig
|
|
308
|
+
},
|
|
309
|
+
(error) => Promise.reject(error)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
clientInstance.interceptors.response.use(
|
|
313
|
+
(response) => response,
|
|
314
|
+
async (error) => {
|
|
315
|
+
if (isTransientServerError(error.response?.status)) {
|
|
316
|
+
const originalRequest = error.config as RetriableRequestConfig | undefined
|
|
317
|
+
const retryCount = originalRequest?._serverRetryCount ?? 0
|
|
318
|
+
|
|
319
|
+
if (originalRequest && retryCount < maxServerRetries && isIdempotentMethod(originalRequest.method)) {
|
|
320
|
+
originalRequest._serverRetryCount = retryCount + 1
|
|
321
|
+
await delay(serverRetryBaseDelayMs * Math.pow(2, retryCount))
|
|
322
|
+
return clientInstance(originalRequest)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (error.response?.status === 428) {
|
|
327
|
+
const originalRequest = error.config as RetriableRequestConfig | undefined
|
|
328
|
+
const requestUrl = originalRequest?.url
|
|
329
|
+
const responsePayload = error.response?.data
|
|
330
|
+
const missingPolicies = parseMissingPolicies(responsePayload)
|
|
331
|
+
const canAttemptPolicyAcceptance = Boolean(
|
|
332
|
+
originalRequest &&
|
|
333
|
+
!originalRequest._policyRetry &&
|
|
334
|
+
!isPolicyAcceptRequest(requestUrl) &&
|
|
335
|
+
isPoliciesPendingErrorPayload(responsePayload) &&
|
|
336
|
+
missingPolicies.length > 0
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if (canAttemptPolicyAcceptance && originalRequest) {
|
|
340
|
+
originalRequest._policyRetry = true
|
|
341
|
+
const accepted = await acceptMissingPolicies(missingPolicies)
|
|
342
|
+
if (accepted) {
|
|
343
|
+
return clientInstance(originalRequest)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (error.response?.status === 401) {
|
|
349
|
+
const originalRequest = error.config as RetriableRequestConfig | undefined
|
|
350
|
+
const requestUrl = originalRequest?.url
|
|
351
|
+
const canAttemptRefresh = Boolean(
|
|
352
|
+
originalRequest &&
|
|
353
|
+
!originalRequest._retry &&
|
|
354
|
+
!tokenStorage.shouldSkipAuth(requestUrl) &&
|
|
355
|
+
!tokenStorage.isRefreshTokenEndpoint(requestUrl)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
if (canAttemptRefresh && originalRequest) {
|
|
359
|
+
originalRequest._retry = true
|
|
360
|
+
const refreshedToken = await tokenStorage.tryRefreshToken()
|
|
361
|
+
|
|
362
|
+
if (refreshedToken) {
|
|
363
|
+
const retryConfig = withAuthorizationHeader(originalRequest, refreshedToken)
|
|
364
|
+
cancelQueuedAuthFailureRedirect()
|
|
365
|
+
pendingAuthRequests = []
|
|
366
|
+
return clientInstance(retryConfig)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (tokenStorage.wasTokenRotatedSince(originalRequest?.headers)) {
|
|
371
|
+
onResponseError?.(error)
|
|
372
|
+
return Promise.reject(error)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const shouldCapturePendingRequest = Boolean(
|
|
376
|
+
originalRequest &&
|
|
377
|
+
!tokenStorage.shouldSkipAuth(requestUrl) &&
|
|
378
|
+
!tokenStorage.isRefreshTokenEndpoint(requestUrl)
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if (shouldCapturePendingRequest && originalRequest) {
|
|
382
|
+
capturePendingAuthRequest(originalRequest)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
queueAuthFailureRedirect()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
onResponseError?.(error)
|
|
389
|
+
|
|
390
|
+
return Promise.reject(error)
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
async get<T = unknown>(url: string, reqConfig?: RequestConfig): Promise<ApiResponse<T>> {
|
|
396
|
+
const response = await clientInstance.get<ApiResponse<T>>(url, reqConfig as AxiosRequestConfig)
|
|
397
|
+
return response.data
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
async post<T = unknown>(url: string, data?: unknown, reqConfig?: RequestConfig): Promise<ApiResponse<T>> {
|
|
401
|
+
const response = await clientInstance.post<ApiResponse<T>>(url, data, reqConfig as AxiosRequestConfig)
|
|
402
|
+
return response.data
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
async put<T = unknown>(url: string, data?: unknown, reqConfig?: RequestConfig): Promise<ApiResponse<T>> {
|
|
406
|
+
const response = await clientInstance.put<ApiResponse<T>>(url, data, reqConfig as AxiosRequestConfig)
|
|
407
|
+
return response.data
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
async patch<T = unknown>(url: string, data?: unknown, reqConfig?: RequestConfig): Promise<ApiResponse<T>> {
|
|
411
|
+
const response = await clientInstance.patch<ApiResponse<T>>(url, data, reqConfig as AxiosRequestConfig)
|
|
412
|
+
return response.data
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
async delete<T = unknown>(url: string, reqConfig?: RequestConfig): Promise<ApiResponse<T>> {
|
|
416
|
+
const response = await clientInstance.delete<ApiResponse<T>>(url, reqConfig as AxiosRequestConfig)
|
|
417
|
+
return response.data
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
async postForm<T = unknown>(url: string, form: FormData, reqConfig?: RequestConfig): Promise<T> {
|
|
421
|
+
const response = await clientInstance.post<T>(url, form, reqConfig as AxiosRequestConfig)
|
|
422
|
+
return response.data
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
async postFormRaw<T = unknown>(url: string, form: FormData, reqConfig?: RequestConfig): Promise<AxiosResponse<T>> {
|
|
426
|
+
const response = await clientInstance.post<T>(url, form, reqConfig as AxiosRequestConfig)
|
|
427
|
+
return response
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
markAuthRecoveryHandled(): void {
|
|
431
|
+
cancelQueuedAuthFailureRedirect()
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
hasPendingAuthRequest(): boolean {
|
|
435
|
+
return pendingAuthRequests.length > 0
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
clearPendingAuthRequest(): void {
|
|
439
|
+
pendingAuthRequests = []
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
async replayPendingAuthRequest(token: string): Promise<void> {
|
|
443
|
+
if (pendingAuthRequests.length === 0) {
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
cancelQueuedAuthFailureRedirect()
|
|
448
|
+
|
|
449
|
+
const requestsToReplay = pendingAuthRequests.map(cloneRequestConfig)
|
|
450
|
+
pendingAuthRequests = []
|
|
451
|
+
|
|
452
|
+
const results = await Promise.allSettled(
|
|
453
|
+
requestsToReplay.map((request) => {
|
|
454
|
+
const retryConfig = withAuthorizationHeader({
|
|
455
|
+
...request,
|
|
456
|
+
_retry: true
|
|
457
|
+
}, token)
|
|
458
|
+
|
|
459
|
+
return clientInstance(retryConfig)
|
|
460
|
+
})
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
const firstRejection = results.find(
|
|
464
|
+
(result): result is PromiseRejectedResult => result.status === 'rejected'
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
if (firstRejection) {
|
|
468
|
+
throw firstRejection.reason as Error
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|