@wheeparam/library 0.0.4 → 0.0.6
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/api/index.d.cts +13 -1
- package/dist/api/index.d.ts +13 -1
- package/dist/client/index.cjs +239 -102
- package/dist/client/index.d.cts +95 -82
- package/dist/client/index.d.ts +95 -82
- package/dist/client/index.js +242 -103
- package/package.json +1 -1
package/dist/api/index.d.cts
CHANGED
|
@@ -72,6 +72,18 @@ type Route<Path extends string = string> = {
|
|
|
72
72
|
* ] as const)
|
|
73
73
|
*/
|
|
74
74
|
declare function defineRoutes<const R extends readonly Route<string>[]>(routes: R): R;
|
|
75
|
+
type ApiProps = {
|
|
76
|
+
params: Promise<{
|
|
77
|
+
slug?: string[];
|
|
78
|
+
}>;
|
|
79
|
+
};
|
|
80
|
+
type ListResponse<T> = {
|
|
81
|
+
result: T[];
|
|
82
|
+
totalCount: number;
|
|
83
|
+
};
|
|
84
|
+
type RowResponse<T> = {
|
|
85
|
+
result: T;
|
|
86
|
+
};
|
|
75
87
|
|
|
76
88
|
/**
|
|
77
89
|
* @packageDocumentation
|
|
@@ -121,4 +133,4 @@ declare function matchRoute(path: string, slug: string[]): MatchResult | null;
|
|
|
121
133
|
*/
|
|
122
134
|
declare function routeResponse(slug: string[], routes: readonly Route<string>[], request: Request): Promise<Response>;
|
|
123
135
|
|
|
124
|
-
export { type ParamsFromPath, type Route, type RouteHandler, defineRoutes, matchRoute, routeResponse };
|
|
136
|
+
export { type ApiProps, type ListResponse, type ParamsFromPath, type Route, type RouteHandler, type RowResponse, defineRoutes, matchRoute, routeResponse };
|
package/dist/api/index.d.ts
CHANGED
|
@@ -72,6 +72,18 @@ type Route<Path extends string = string> = {
|
|
|
72
72
|
* ] as const)
|
|
73
73
|
*/
|
|
74
74
|
declare function defineRoutes<const R extends readonly Route<string>[]>(routes: R): R;
|
|
75
|
+
type ApiProps = {
|
|
76
|
+
params: Promise<{
|
|
77
|
+
slug?: string[];
|
|
78
|
+
}>;
|
|
79
|
+
};
|
|
80
|
+
type ListResponse<T> = {
|
|
81
|
+
result: T[];
|
|
82
|
+
totalCount: number;
|
|
83
|
+
};
|
|
84
|
+
type RowResponse<T> = {
|
|
85
|
+
result: T;
|
|
86
|
+
};
|
|
75
87
|
|
|
76
88
|
/**
|
|
77
89
|
* @packageDocumentation
|
|
@@ -121,4 +133,4 @@ declare function matchRoute(path: string, slug: string[]): MatchResult | null;
|
|
|
121
133
|
*/
|
|
122
134
|
declare function routeResponse(slug: string[], routes: readonly Route<string>[], request: Request): Promise<Response>;
|
|
123
135
|
|
|
124
|
-
export { type ParamsFromPath, type Route, type RouteHandler, defineRoutes, matchRoute, routeResponse };
|
|
136
|
+
export { type ApiProps, type ListResponse, type ParamsFromPath, type Route, type RouteHandler, type RowResponse, defineRoutes, matchRoute, routeResponse };
|
package/dist/client/index.cjs
CHANGED
|
@@ -34,169 +34,306 @@ __export(client_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(client_exports);
|
|
36
36
|
|
|
37
|
-
// src/client/
|
|
37
|
+
// src/client/createAxiosClient.ts
|
|
38
38
|
var import_axios = __toESM(require("axios"), 1);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
|
|
40
|
+
// src/client/error.ts
|
|
41
|
+
var defaultShouldRefresh = (error) => {
|
|
42
|
+
return error.response?.status === 401;
|
|
43
|
+
};
|
|
44
|
+
function defaultParseRefreshResponse(data) {
|
|
45
|
+
const source = typeof data === "object" && data !== null ? data : {};
|
|
46
|
+
const accessToken = typeof source.result?.accessToken === "string" && source.result.accessToken || typeof source.accessToken === "string" && source.accessToken || typeof source.result?.token === "string" && source.result.token || typeof source.token === "string" && source.token || "";
|
|
47
|
+
const refreshToken = typeof source.result?.refreshToken === "string" && source.result.refreshToken || typeof source.refreshToken === "string" && source.refreshToken || void 0;
|
|
48
|
+
if (!accessToken) {
|
|
49
|
+
throw new Error("Refresh response does not contain accessToken");
|
|
50
|
+
}
|
|
42
51
|
return {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
accessToken,
|
|
53
|
+
refreshToken
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function getDefaultErrorMessage(error) {
|
|
57
|
+
const axiosError = error;
|
|
58
|
+
const status = axiosError.response?.status;
|
|
59
|
+
const serverMessage = axiosError.response?.data?.error;
|
|
60
|
+
if (serverMessage && serverMessage.trim().length > 0) {
|
|
61
|
+
return serverMessage;
|
|
62
|
+
}
|
|
63
|
+
switch (status) {
|
|
64
|
+
case 0:
|
|
65
|
+
return "REST API \uC11C\uBC84\uC5D0 \uC811\uADFC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n\uC11C\uBC84 \uAD00\uB9AC\uC790\uC5D0\uAC8C \uBB38\uC758\uD558\uC138\uC694.";
|
|
66
|
+
case 400:
|
|
67
|
+
return "\uC798\uBABB\uB41C \uC694\uCCAD\uC785\uB2C8\uB2E4.";
|
|
68
|
+
case 401:
|
|
69
|
+
return "\uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
|
|
70
|
+
case 403:
|
|
71
|
+
return "\uC811\uADFC \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
|
|
72
|
+
case 404:
|
|
73
|
+
return "[404] REST API \uC694\uCCAD\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4.";
|
|
74
|
+
case 500:
|
|
75
|
+
return "\uC11C\uBC84\uC5D0\uC11C \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD558\uC600\uC2B5\uB2C8\uB2E4.";
|
|
76
|
+
default:
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/client/refreshManager.ts
|
|
82
|
+
var RefreshManager = class {
|
|
83
|
+
constructor() {
|
|
84
|
+
this.isRefreshing = false;
|
|
85
|
+
this.subscribers = [];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 현재 refresh 진행 중인지 반환합니다.
|
|
89
|
+
*/
|
|
90
|
+
get refreshing() {
|
|
91
|
+
return this.isRefreshing;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* refresh 시작 상태로 전환합니다.
|
|
95
|
+
*/
|
|
96
|
+
begin() {
|
|
97
|
+
this.isRefreshing = true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* refresh 종료 상태로 전환합니다.
|
|
101
|
+
*/
|
|
102
|
+
end() {
|
|
103
|
+
this.isRefreshing = false;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* refresh 완료를 기다리는 subscriber를 등록합니다.
|
|
107
|
+
*
|
|
108
|
+
* @param callback refresh 완료 후 호출될 함수
|
|
109
|
+
*/
|
|
110
|
+
subscribe(callback) {
|
|
111
|
+
this.subscribers.push(callback);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 대기 중인 subscriber들에게 결과를 알리고 큐를 초기화합니다.
|
|
115
|
+
*
|
|
116
|
+
* @param token 새 access token
|
|
117
|
+
*/
|
|
118
|
+
notify(token) {
|
|
119
|
+
this.subscribers.forEach((callback) => callback(token));
|
|
120
|
+
this.subscribers = [];
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
async function requestRefresh(params) {
|
|
124
|
+
const {
|
|
125
|
+
refreshUrl,
|
|
126
|
+
withCredentials,
|
|
127
|
+
refreshStrategy,
|
|
128
|
+
refreshToken,
|
|
129
|
+
parseRefreshResponse
|
|
130
|
+
} = params;
|
|
131
|
+
const requestInit = {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: {
|
|
134
|
+
"Content-Type": "application/json"
|
|
48
135
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
136
|
+
credentials: withCredentials ? "include" : "same-origin"
|
|
137
|
+
};
|
|
138
|
+
if (refreshStrategy === "body") {
|
|
139
|
+
if (!refreshToken) {
|
|
140
|
+
throw new Error("Refresh token not found");
|
|
141
|
+
}
|
|
142
|
+
requestInit.body = JSON.stringify({ refreshToken });
|
|
143
|
+
}
|
|
144
|
+
const response = await fetch(refreshUrl, requestInit);
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
throw new Error("Token refresh failed");
|
|
147
|
+
}
|
|
148
|
+
const json = await response.json();
|
|
149
|
+
return parseRefreshResponse(json);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/client/tokenStorage.ts
|
|
153
|
+
function createAccessTokenAccess(mode, key = "accessToken") {
|
|
154
|
+
let memoryToken = null;
|
|
155
|
+
if (mode === "localStorage") {
|
|
156
|
+
return {
|
|
157
|
+
get: () => window.localStorage.getItem(key),
|
|
158
|
+
set: (token) => {
|
|
159
|
+
if (!token) {
|
|
160
|
+
window.localStorage.removeItem(key);
|
|
161
|
+
} else {
|
|
162
|
+
window.localStorage.setItem(key, token);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
clear: () => {
|
|
166
|
+
window.localStorage.removeItem(key);
|
|
52
167
|
}
|
|
53
|
-
|
|
54
|
-
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (mode === "memory") {
|
|
171
|
+
return {
|
|
172
|
+
get: () => memoryToken,
|
|
173
|
+
set: (token) => {
|
|
174
|
+
memoryToken = token;
|
|
175
|
+
},
|
|
176
|
+
clear: () => {
|
|
177
|
+
memoryToken = null;
|
|
55
178
|
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
get: () => null,
|
|
183
|
+
set: () => {
|
|
56
184
|
},
|
|
57
|
-
clear() {
|
|
58
|
-
window.localStorage.removeItem(accessTokenKey);
|
|
59
|
-
window.localStorage.removeItem(refreshTokenKey);
|
|
185
|
+
clear: () => {
|
|
60
186
|
}
|
|
61
187
|
};
|
|
62
188
|
}
|
|
63
|
-
function
|
|
64
|
-
const refreshTokenKey = keys?.refreshTokenKey ?? "refreshToken";
|
|
189
|
+
function createRefreshTokenAccess(key = "refreshToken") {
|
|
65
190
|
return {
|
|
66
|
-
get()
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
},
|
|
73
|
-
set(data) {
|
|
74
|
-
if (data.refreshToken !== void 0) {
|
|
75
|
-
window.localStorage.setItem(refreshTokenKey, data.refreshToken ?? "");
|
|
191
|
+
get: () => window.localStorage.getItem(key),
|
|
192
|
+
set: (token) => {
|
|
193
|
+
if (!token) {
|
|
194
|
+
window.localStorage.removeItem(key);
|
|
195
|
+
} else {
|
|
196
|
+
window.localStorage.setItem(key, token);
|
|
76
197
|
}
|
|
77
198
|
},
|
|
78
|
-
clear() {
|
|
79
|
-
window.localStorage.removeItem(
|
|
199
|
+
clear: () => {
|
|
200
|
+
window.localStorage.removeItem(key);
|
|
80
201
|
}
|
|
81
202
|
};
|
|
82
203
|
}
|
|
204
|
+
|
|
205
|
+
// src/client/createAxiosClient.ts
|
|
206
|
+
function setAuthorizationHeader(config, accessToken) {
|
|
207
|
+
const headers = new import_axios.AxiosHeaders();
|
|
208
|
+
if (config.headers) {
|
|
209
|
+
const current = config.headers instanceof import_axios.AxiosHeaders ? config.headers.toJSON() : config.headers;
|
|
210
|
+
for (const [key, value] of Object.entries(current)) {
|
|
211
|
+
if (value !== void 0) {
|
|
212
|
+
headers.set(key, String(value));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
217
|
+
config.headers = headers;
|
|
218
|
+
return config;
|
|
219
|
+
}
|
|
83
220
|
function createAxiosClient(options) {
|
|
84
221
|
const {
|
|
85
222
|
baseURL,
|
|
86
223
|
timeout = 1e4,
|
|
87
224
|
withCredentials = true,
|
|
88
225
|
refreshUrl,
|
|
89
|
-
|
|
226
|
+
accessTokenStorage = "localStorage",
|
|
227
|
+
refreshStrategy = "body",
|
|
90
228
|
storageKeys,
|
|
91
229
|
onAuthStateChange,
|
|
92
230
|
onError,
|
|
93
|
-
getErrorMessage
|
|
231
|
+
getErrorMessage,
|
|
232
|
+
shouldRefresh = defaultShouldRefresh,
|
|
233
|
+
parseRefreshResponse = defaultParseRefreshResponse
|
|
94
234
|
} = options;
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
235
|
+
const accessTokenKey = storageKeys?.accessTokenKey ?? "accessToken";
|
|
236
|
+
const refreshTokenKey = storageKeys?.refreshTokenKey ?? "refreshToken";
|
|
237
|
+
const instance = import_axios.default.create({
|
|
238
|
+
baseURL,
|
|
239
|
+
timeout,
|
|
240
|
+
withCredentials
|
|
241
|
+
});
|
|
242
|
+
const accessTokenAccess = createAccessTokenAccess(accessTokenStorage, accessTokenKey);
|
|
243
|
+
const refreshTokenAccess = createRefreshTokenAccess(refreshTokenKey);
|
|
244
|
+
const refreshManager = new RefreshManager();
|
|
103
245
|
const clearAuth = () => {
|
|
104
|
-
|
|
246
|
+
accessTokenAccess.clear();
|
|
247
|
+
if (refreshStrategy === "body") {
|
|
248
|
+
refreshTokenAccess.clear();
|
|
249
|
+
}
|
|
105
250
|
onAuthStateChange?.("unauthenticated");
|
|
106
251
|
};
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
252
|
+
const getToken = () => {
|
|
253
|
+
return {
|
|
254
|
+
accessToken: accessTokenAccess.get(),
|
|
255
|
+
refreshToken: refreshStrategy === "body" ? refreshTokenAccess.get() : null
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
const setToken = (data) => {
|
|
259
|
+
if (data.accessToken !== void 0) {
|
|
260
|
+
accessTokenAccess.set(data.accessToken ?? null);
|
|
261
|
+
}
|
|
262
|
+
if (refreshStrategy === "body" && data.refreshToken !== void 0) {
|
|
263
|
+
refreshTokenAccess.set(data.refreshToken ?? null);
|
|
116
264
|
}
|
|
117
|
-
return await res.json();
|
|
118
265
|
};
|
|
119
266
|
instance.interceptors.request.use(
|
|
120
267
|
(config) => {
|
|
121
|
-
const
|
|
268
|
+
const accessToken = accessTokenAccess.get();
|
|
122
269
|
if (accessToken) {
|
|
123
|
-
config
|
|
124
|
-
config.headers.Authorization = `Bearer ${accessToken}`;
|
|
270
|
+
setAuthorizationHeader(config, accessToken);
|
|
125
271
|
}
|
|
126
272
|
return config;
|
|
127
273
|
},
|
|
128
|
-
(
|
|
274
|
+
(error) => Promise.reject(error)
|
|
129
275
|
);
|
|
130
276
|
instance.interceptors.response.use(
|
|
131
|
-
(
|
|
277
|
+
(response) => response,
|
|
132
278
|
async (error) => {
|
|
133
|
-
const response = error.response;
|
|
134
|
-
const status = response?.status;
|
|
135
279
|
const originalRequest = error.config ?? {};
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
case 0:
|
|
141
|
-
message = "REST API \uC11C\uBC84\uC5D0 \uC811\uADFC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4\n\uC11C\uBC84 \uAD00\uB9AC\uC790\uC5D0\uAC8C \uBB38\uC758\uD558\uC138\uC694";
|
|
142
|
-
break;
|
|
143
|
-
case 400:
|
|
144
|
-
message = "\uC798\uBABB\uB41C \uC694\uCCAD\uC785\uB2C8\uB2E4.";
|
|
145
|
-
break;
|
|
146
|
-
case 404:
|
|
147
|
-
message = "[404] REST API \uC694\uCCAD\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4";
|
|
148
|
-
break;
|
|
149
|
-
case 500:
|
|
150
|
-
message = "\uC11C\uBC84\uC5D0\uC11C \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD558\uC600\uC2B5\uB2C8\uB2E4.";
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
280
|
+
if (!shouldRefresh(error)) {
|
|
281
|
+
const message = getErrorMessage?.(error) ?? getDefaultErrorMessage(error);
|
|
282
|
+
if (message) {
|
|
283
|
+
onError?.(message, error);
|
|
153
284
|
}
|
|
154
|
-
if (message) onError?.(message, error);
|
|
155
285
|
return Promise.reject(error);
|
|
156
286
|
}
|
|
157
|
-
|
|
287
|
+
const requestUrl = originalRequest.url ?? "";
|
|
288
|
+
if (typeof requestUrl === "string" && requestUrl.includes(refreshUrl)) {
|
|
158
289
|
clearAuth();
|
|
159
290
|
return Promise.reject(error);
|
|
160
291
|
}
|
|
161
|
-
originalRequest._retry
|
|
162
|
-
const { refreshToken } = tokenAccess.get();
|
|
163
|
-
if (!refreshToken) {
|
|
292
|
+
if (originalRequest._retry) {
|
|
164
293
|
clearAuth();
|
|
165
294
|
return Promise.reject(error);
|
|
166
295
|
}
|
|
167
|
-
|
|
296
|
+
originalRequest._retry = true;
|
|
297
|
+
if (refreshManager.refreshing) {
|
|
168
298
|
return new Promise((resolve, reject) => {
|
|
169
|
-
|
|
170
|
-
if (!
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
299
|
+
refreshManager.subscribe((newAccessToken) => {
|
|
300
|
+
if (!newAccessToken) {
|
|
301
|
+
reject(error);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const retryRequest = setAuthorizationHeader(originalRequest, newAccessToken);
|
|
305
|
+
resolve(instance(retryRequest));
|
|
174
306
|
});
|
|
175
307
|
});
|
|
176
308
|
}
|
|
177
|
-
|
|
309
|
+
refreshManager.begin();
|
|
178
310
|
try {
|
|
179
|
-
const newTokens = await requestRefresh(
|
|
180
|
-
|
|
311
|
+
const newTokens = await requestRefresh({
|
|
312
|
+
refreshUrl,
|
|
313
|
+
withCredentials,
|
|
314
|
+
refreshStrategy,
|
|
315
|
+
refreshToken: refreshStrategy === "body" ? refreshTokenAccess.get() : null,
|
|
316
|
+
parseRefreshResponse
|
|
317
|
+
});
|
|
318
|
+
setToken(newTokens);
|
|
181
319
|
onAuthStateChange?.("authenticated");
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
notifySubscribers(null);
|
|
320
|
+
refreshManager.end();
|
|
321
|
+
refreshManager.notify(newTokens.accessToken);
|
|
322
|
+
const retryRequest = setAuthorizationHeader(originalRequest, newTokens.accessToken);
|
|
323
|
+
return instance(retryRequest);
|
|
324
|
+
} catch (refreshError) {
|
|
325
|
+
refreshManager.end();
|
|
326
|
+
refreshManager.notify(null);
|
|
190
327
|
clearAuth();
|
|
191
|
-
return Promise.reject(
|
|
328
|
+
return Promise.reject(refreshError);
|
|
192
329
|
}
|
|
193
330
|
}
|
|
194
331
|
);
|
|
195
332
|
return {
|
|
196
333
|
axios: instance,
|
|
197
|
-
setToken
|
|
198
|
-
getToken
|
|
199
|
-
clearToken:
|
|
334
|
+
setToken,
|
|
335
|
+
getToken,
|
|
336
|
+
clearToken: clearAuth
|
|
200
337
|
};
|
|
201
338
|
}
|
|
202
339
|
// Annotate the CommonJS export names for ESM import in node:
|