-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathclient.ts
More file actions
98 lines (91 loc) · 2.77 KB
/
Copy pathclient.ts
File metadata and controls
98 lines (91 loc) · 2.77 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
import ky, { type KyInstance } from 'ky';
const getApiBaseUrl = () => {
if (typeof window !== 'undefined') {
return window.location.origin;
}
const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;
if (!apiBaseUrl) {
throw new Error('NEXT_PUBLIC_API_BASE_URL is not set');
}
return apiBaseUrl;
};
const REFRESH_PATH = 'api/v1/users/refresh';
const PRE_AUTH_PATHS = ['api/v1/users/login', 'api/v1/users/signup', 'api/v1/auth/oauth', 'api/v1/auth/social/signup'];
const SKIP_AUTO_REDIRECT_PATHS = ['api/v1/chatbot/chat'];
const isPreAuth = (url: string) => PRE_AUTH_PATHS.some((p) => url.includes(p));
const isRefresh = (url: string) => url.includes(REFRESH_PATH);
const skipsAutoRedirect = (url: string) => {
try {
const normalized = new URL(url).pathname.replace(/^\//, '');
return SKIP_AUTO_REDIRECT_PATHS.includes(normalized);
} catch {
return false;
}
};
let refreshPromise: Promise<Response> | null = null;
const triggerRefresh = () => {
if (!refreshPromise) {
refreshPromise = fetch(`${getApiBaseUrl()}/${REFRESH_PATH}`, {
method: 'POST',
credentials: 'include',
}).finally(() => {
refreshPromise = null;
});
}
return refreshPromise;
};
const createClient = () =>
ky.create({
prefixUrl: getApiBaseUrl(),
timeout: 10000,
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
retry: {
limit: 1,
methods: ['get', 'post', 'put', 'patch', 'delete', 'head'],
statusCodes: [401],
},
hooks: {
beforeRetry: [
async ({ request, error }) => {
const status = (error as { status?: number }).status;
if (status !== 401) {
throw error;
}
if (isPreAuth(request.url) || isRefresh(request.url)) {
throw error;
}
const refreshRes = await triggerRefresh();
if (!refreshRes.ok) {
if (!skipsAutoRedirect(request.url) && typeof window !== 'undefined') {
window.location.href = '/login';
}
throw error;
}
},
],
afterResponse: [
async (_request, _options, response) => {
if (!response.ok) {
const body = await response
.clone()
.json()
.catch(() => null);
const error = new Error(`API Error: ${response.status}`);
Object.assign(error, { status: response.status, body });
throw error;
}
},
],
},
});
export const client = new Proxy((() => createClient()) as unknown as KyInstance, {
apply(_target, _thisArg, argArray) {
return Reflect.apply(createClient(), undefined, argArray);
},
get(_target, property) {
return Reflect.get(createClient(), property);
},
});