@wheeparam/library 0.0.5 → 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.
@@ -34,169 +34,306 @@ __export(client_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(client_exports);
36
36
 
37
- // src/client/axios.ts
37
+ // src/client/createAxiosClient.ts
38
38
  var import_axios = __toESM(require("axios"), 1);
39
- function createLocalStorageTokenAccess(keys) {
40
- const accessTokenKey = keys?.accessTokenKey ?? "accessToken";
41
- const refreshTokenKey = keys?.refreshTokenKey ?? "refreshToken";
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
- get() {
44
- return {
45
- accessToken: window.localStorage.getItem(accessTokenKey),
46
- refreshToken: window.localStorage.getItem(refreshTokenKey)
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
- set(data) {
50
- if (data.accessToken !== void 0) {
51
- window.localStorage.setItem(accessTokenKey, data.accessToken ?? "");
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
- if (data.refreshToken !== void 0) {
54
- window.localStorage.setItem(refreshTokenKey, data.refreshToken ?? "");
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 createCookieModeTokenAccess(keys) {
64
- const refreshTokenKey = keys?.refreshTokenKey ?? "refreshToken";
189
+ function createRefreshTokenAccess(key = "refreshToken") {
65
190
  return {
66
- get() {
67
- return {
68
- accessToken: null,
69
- // 쿠키 기반이면 JS에서 accessToken을 직접 읽지 않는 전제를 둔다.
70
- refreshToken: window.localStorage.getItem(refreshTokenKey)
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(refreshTokenKey);
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
- storage = "localStorage",
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 instance = import_axios.default.create({ baseURL, timeout, withCredentials });
96
- const tokenAccess = storage === "localStorage" ? createLocalStorageTokenAccess(storageKeys) : createCookieModeTokenAccess({ refreshTokenKey: storageKeys?.refreshTokenKey });
97
- let isRefreshing = false;
98
- let subscribers = [];
99
- const notifySubscribers = (token) => {
100
- subscribers.forEach((cb) => cb(token));
101
- subscribers = [];
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
- tokenAccess.clear();
246
+ accessTokenAccess.clear();
247
+ if (refreshStrategy === "body") {
248
+ refreshTokenAccess.clear();
249
+ }
105
250
  onAuthStateChange?.("unauthenticated");
106
251
  };
107
- const requestRefresh = async (refreshToken) => {
108
- const res = await fetch(refreshUrl, {
109
- method: "POST",
110
- headers: { "Content-Type": "application/json" },
111
- body: JSON.stringify({ refreshToken }),
112
- credentials: withCredentials ? "include" : "same-origin"
113
- });
114
- if (!res.ok) {
115
- throw new Error("Token refresh failed");
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 { accessToken } = tokenAccess.get();
268
+ const accessToken = accessTokenAccess.get();
122
269
  if (accessToken) {
123
- config.headers = config.headers ?? {};
124
- config.headers.Authorization = `Bearer ${accessToken}`;
270
+ setAuthorizationHeader(config, accessToken);
125
271
  }
126
272
  return config;
127
273
  },
128
- (e) => Promise.reject(e)
274
+ (error) => Promise.reject(error)
129
275
  );
130
276
  instance.interceptors.response.use(
131
- (res) => res,
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 (status !== 401) {
137
- let message = getErrorMessage?.(error) ?? response?.data?.error ?? "";
138
- if (!message && typeof status === "number") {
139
- switch (status) {
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
- if (originalRequest._retry) {
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 = true;
162
- const { refreshToken } = tokenAccess.get();
163
- if (!refreshToken) {
292
+ if (originalRequest._retry) {
164
293
  clearAuth();
165
294
  return Promise.reject(error);
166
295
  }
167
- if (isRefreshing) {
296
+ originalRequest._retry = true;
297
+ if (refreshManager.refreshing) {
168
298
  return new Promise((resolve, reject) => {
169
- subscribers.push((newToken) => {
170
- if (!newToken) return reject(error);
171
- originalRequest.headers = originalRequest.headers ?? {};
172
- originalRequest.headers.Authorization = `Bearer ${newToken}`;
173
- resolve(instance(originalRequest));
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
- isRefreshing = true;
309
+ refreshManager.begin();
178
310
  try {
179
- const newTokens = await requestRefresh(refreshToken);
180
- tokenAccess.set(newTokens);
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
- isRefreshing = false;
183
- notifySubscribers(newTokens.accessToken);
184
- originalRequest.headers = originalRequest.headers ?? {};
185
- originalRequest.headers.Authorization = `Bearer ${newTokens.accessToken}`;
186
- return instance(originalRequest);
187
- } catch (e) {
188
- isRefreshing = false;
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(error);
328
+ return Promise.reject(refreshError);
192
329
  }
193
330
  }
194
331
  );
195
332
  return {
196
333
  axios: instance,
197
- setToken: (data) => tokenAccess.set(data),
198
- getToken: () => tokenAccess.get(),
199
- clearToken: () => clearAuth()
334
+ setToken,
335
+ getToken,
336
+ clearToken: clearAuth
200
337
  };
201
338
  }
202
339
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,144 +1,173 @@
1
- import { AxiosInstance } from 'axios';
1
+ import { AxiosInstance, AxiosError } from 'axios';
2
2
 
3
3
  /**
4
4
  * @packageDocumentation
5
- * Axios Client (Client-only)
6
- *
7
- * 공통 요구사항:
8
- * - accessToken이 있으면 Authorization 헤더에 첨부
9
- * - 401 발생 시 refreshToken으로 토큰 재발급을 시도하고, 원래 요청을 재시도
10
- * - refresh 중복 호출 방지(큐 방식)
11
- *
12
- * 단순화 정책:
13
- * - refresh URL만 옵션으로 받아 내부에서 fetch로 토큰 재발급을 수행한다.
14
- * - 토큰 저장은 "localStorage" 또는 "cookie" 2가지 모드만 지원한다.
15
- *
16
- * ⚠️ 이 모듈은 브라우저 전용이다(window/localStorage 사용 가능).
17
- * Next.js에서는 반드시 Client Component 또는 클라이언트 전용 모듈에서 import 해야 한다.
5
+ * Axios client type definitions.
18
6
  */
19
7
 
20
8
  /**
21
- * 토큰 저장 방식
22
- * - localStorage: accessToken/refreshToken을 localStorage에 저장
23
- * - cookie: 토큰을 쿠키 기반으로 운용(클라 저장 최소화)
9
+ * AccessToken 저장 방식
10
+ *
11
+ * - localStorage: 브라우저 localStorage에 저장
12
+ * - memory: 현재 탭 메모리에만 저장
13
+ * - none: 저장하지 않음
24
14
  */
25
- type TokenStorageMode = "localStorage" | "cookie";
15
+ type AccessTokenStorageMode = "localStorage" | "memory" | "none";
26
16
  /**
27
- * refresh 요청/응답 타입
17
+ * Refresh 요청 전략
18
+ *
19
+ * - body:
20
+ * refreshToken을 클라이언트가 보관하고 있다가 body에 포함해 전송
21
+ *
22
+ * - cookieOnly:
23
+ * refreshToken을 클라이언트 JS가 보관하지 않고,
24
+ * HttpOnly Cookie 등 서버가 읽을 수 있는 방식만 사용
25
+ */
26
+ type RefreshStrategy = "body" | "cookieOnly";
27
+ /**
28
+ * Refresh 요청 body
28
29
  */
29
30
  type RefreshRequestBody = {
30
31
  refreshToken: string;
31
32
  };
33
+ /**
34
+ * Refresh 응답 body
35
+ */
32
36
  type RefreshResponseBody = {
33
37
  accessToken: string;
34
- refreshToken: string;
38
+ refreshToken?: string;
35
39
  };
36
40
  /**
37
- * createAxiosClient 옵션
41
+ * 로그인 상태
42
+ */
43
+ type AuthState = "authenticated" | "unauthenticated";
44
+ /**
45
+ * localStorage key 설정
46
+ */
47
+ type TokenStorageKeys = {
48
+ accessTokenKey?: string;
49
+ refreshTokenKey?: string;
50
+ };
51
+ /**
52
+ * refresh 여부 판별 함수
53
+ */
54
+ type ShouldRefreshFn = (error: AxiosError<unknown>) => boolean;
55
+ /**
56
+ * refresh 응답 파싱 함수
57
+ */
58
+ type ParseRefreshResponseFn = (data: unknown) => RefreshResponseBody;
59
+ /**
60
+ * axios client 생성 옵션
38
61
  */
39
62
  type CreateAxiosClientOptions = {
40
63
  /**
41
- * axios baseURL (선택)
64
+ * axios baseURL
42
65
  */
43
66
  baseURL?: string;
44
67
  /**
45
- * 요청 타임아웃(ms)
68
+ * 요청 timeout(ms)
46
69
  * @default 10000
47
70
  */
48
71
  timeout?: number;
49
72
  /**
50
73
  * 쿠키 전송 여부
51
- * - cookie 모드에서는 거의 필수
52
74
  * @default true
53
75
  */
54
76
  withCredentials?: boolean;
55
77
  /**
56
- * 토큰 재발급(Refresh) API URL
78
+ * refresh API URL
57
79
  * @example "/api/authorize/token"
58
80
  */
59
81
  refreshUrl: string;
60
82
  /**
61
- * 토큰 저장 방식
83
+ * accessToken 저장 방식
62
84
  * @default "localStorage"
63
85
  */
64
- storage?: TokenStorageMode;
86
+ accessTokenStorage?: AccessTokenStorageMode;
65
87
  /**
66
- * localStorage 키 이름(선택)
67
- * @default { accessTokenKey: "accessToken", refreshTokenKey: "refreshToken" }
88
+ * refresh 전략
89
+ * @default "body"
68
90
  */
69
- storageKeys?: {
70
- accessTokenKey?: string;
71
- refreshTokenKey?: string;
72
- };
91
+ refreshStrategy?: RefreshStrategy;
92
+ /**
93
+ * 저장 key 이름
94
+ */
95
+ storageKeys?: TokenStorageKeys;
73
96
  /**
74
- * 로그인 상태 변화 콜백(선택)
75
- * - 프로젝트의 auth context/ref/store 업데이트에 사용
97
+ * 인증 상태 변경 콜백
76
98
  */
77
- onAuthStateChange?: (status: "authenticated" | "unauthenticated") => void;
99
+ onAuthStateChange?: (status: AuthState) => void;
78
100
  /**
79
- * 일반 에러 표시 콜백(선택)
80
- * - Swal/toast 등 프로젝트 스타일로 구현
101
+ * 일반 에러 출력 콜백
81
102
  */
82
103
  onError?: (message: string, error: unknown) => void;
83
104
  /**
84
- * API 에러 메시지 추출 커스터마이즈(선택)
85
- * - null 반환 시 내부 기본 로직 사용
105
+ * 에러 메시지 추출 커스터마이즈
86
106
  */
87
107
  getErrorMessage?: (error: unknown) => string | null;
108
+ /**
109
+ * refresh 여부 판별 커스터마이즈
110
+ */
111
+ shouldRefresh?: ShouldRefreshFn;
112
+ /**
113
+ * refresh 응답 파싱 커스터마이즈
114
+ */
115
+ parseRefreshResponse?: ParseRefreshResponseFn;
88
116
  };
89
117
  /**
90
- * createAxiosClient 반환값
118
+ * createAxiosClient 반환 타입
91
119
  */
92
120
  type AxiosClient = {
93
121
  /**
94
- * 구성 완료된 axios instance
122
+ * 구성된 axios instance
95
123
  */
96
124
  axios: AxiosInstance;
97
125
  /**
98
- * 토큰 설정(로그인 성공 등)
99
- * - localStorage 모드: localStorage에 저장
100
- * - cookie 모드: 기본 구현은 refreshToken만 저장(필요 시)
126
+ * 토큰 저장
101
127
  */
102
128
  setToken: (data: Partial<RefreshResponseBody>) => void;
103
129
  /**
104
- * 토큰 조회
130
+ * 현재 토큰 조회
105
131
  */
106
132
  getToken: () => {
107
133
  accessToken: string | null;
108
134
  refreshToken: string | null;
109
135
  };
110
136
  /**
111
- * 토큰 제거(로그아웃)
137
+ * 토큰 제거
112
138
  */
113
139
  clearToken: () => void;
114
140
  };
141
+
115
142
  /**
116
- * 공용 axios 클라이언트를 생성한다.
143
+ * 공용 axios client를 생성합니다.
117
144
  *
118
- * 토큰 자동 첨부 + 401 발생 시 refresh 후 재요청을 처리한다.
145
+ * 클라이언트는 다음 기능을 제공합니다.
146
+ * - accessToken 자동 첨부
147
+ * - 401 발생 시 refresh 후 원래 요청 재시도
148
+ * - refresh 중복 호출 방지
119
149
  *
120
- * @example 기본 사용법 (Next.js Client Component)
150
+ * @example
121
151
  * ```ts
122
152
  * "use client";
123
153
  *
124
154
  * import Swal from "sweetalert2";
125
- * import nl2br from "@/utils/common";
126
- * import { authContextRef } from "@/components/providers/auth-provider";
127
- * import { createAxiosClient } from "@your-scope/toolkit/client";
155
+ * import { createAxiosClient } from "@your-scope/your-package/client";
128
156
  *
129
- * export const { axios, setToken, clearToken, getToken } = createAxiosClient({
157
+ * export const { axios, setToken, getToken, clearToken } = createAxiosClient({
130
158
  * refreshUrl: "/api/authorize/token",
131
- * storage: "localStorage",
159
+ * accessTokenStorage: "localStorage",
160
+ * refreshStrategy: "cookieOnly",
161
+ * withCredentials: true,
132
162
  *
133
163
  * onAuthStateChange: (status) => {
134
- * authContextRef.setLoginStatus?.(status);
164
+ * console.log("auth state:", status);
135
165
  * },
136
166
  *
137
167
  * onError: (message) => {
138
168
  * Swal.fire({
139
169
  * title: "오류 발생",
140
- * html: nl2br(message),
141
- * backdrop: false,
170
+ * html: message.replace(/\\n/g, "<br />"),
142
171
  * confirmButtonText: "확인",
143
172
  * });
144
173
  * },
@@ -147,33 +176,17 @@ type AxiosClient = {
147
176
  * export default axios;
148
177
  * ```
149
178
  *
150
- * @example 로그인 성공 후 토큰 저장
179
+ * @example
151
180
  * ```ts
152
- * import { setToken } from "@/lib/axios";
153
- *
154
- * async function login(id: string, pw: string) {
155
- * const res = await fetch("/api/login", {
156
- * method: "POST",
157
- * body: JSON.stringify({ id, pw })
158
- * }).then(r => r.json());
159
- *
160
- * setToken({
161
- * accessToken: res.accessToken,
162
- * refreshToken: res.refreshToken
163
- * });
164
- * }
181
+ * const { axios } = createAxiosClient({
182
+ * refreshUrl: "/api/authorize/token",
183
+ * accessTokenStorage: "localStorage",
184
+ * refreshStrategy: "body",
185
+ * });
165
186
  * ```
166
187
  *
167
- * @example 로그아웃 처리
168
- * ```ts
169
- * import { clearToken } from "@/lib/axios";
170
- *
171
- * function logout() {
172
- * clearToken();
173
- * location.href = "/login";
174
- * }
175
- * ```
188
+ * @param options 생성 옵션
176
189
  */
177
190
  declare function createAxiosClient(options: CreateAxiosClientOptions): AxiosClient;
178
191
 
179
- export { type AxiosClient, type CreateAxiosClientOptions, type RefreshRequestBody, type RefreshResponseBody, type TokenStorageMode, createAxiosClient };
192
+ export { type AccessTokenStorageMode, type AuthState, type AxiosClient, type CreateAxiosClientOptions, type ParseRefreshResponseFn, type RefreshRequestBody, type RefreshResponseBody, type RefreshStrategy, type ShouldRefreshFn, type TokenStorageKeys, createAxiosClient };