@djangocfg/api 2.1.333 → 2.1.335

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/index.d.cts CHANGED
@@ -2,6 +2,16 @@ import { ConsolaInstance } from 'consola';
2
2
  import { ZodError } from 'zod';
3
3
 
4
4
  type StorageMode = 'localStorage' | 'cookie';
5
+ /**
6
+ * User-supplied refresh handler. Receives the current refresh token,
7
+ * must return a fresh access (and optional refresh) pair or null on failure.
8
+ * Set once at app bootstrap via `auth.setRefreshHandler(...)`.
9
+ */
10
+ type RefreshResult = {
11
+ access: string;
12
+ refresh?: string;
13
+ } | null;
14
+ type RefreshHandler = (refreshToken: string) => Promise<RefreshResult>;
5
15
  /**
6
16
  * Global auth/config store. All getters read live state every call —
7
17
  * the interceptor below uses these to attach headers per-request.
@@ -35,7 +45,27 @@ declare const auth: {
35
45
  setBaseUrl(url: string | null): void;
36
46
  getWithCredentials(): boolean;
37
47
  setWithCredentials(value: boolean): void;
48
+ /**
49
+ * Fired when the server returns 401 AND no refresh path recovers it
50
+ * (no refresh token, no refresh handler, refresh failed, or retry
51
+ * still 401). The app should clear local state and redirect to login.
52
+ *
53
+ * NOT fired for 401 that gets transparently recovered by the refresh
54
+ * handler — those are invisible to callers.
55
+ */
38
56
  onUnauthorized(cb: ((response: Response) => void) | null): void;
57
+ /**
58
+ * Register the refresh strategy. The handler receives the current
59
+ * refresh token and must call your refresh endpoint, returning
60
+ * `{ access, refresh? }` on success or `null` on failure.
61
+ *
62
+ * @example
63
+ * auth.setRefreshHandler(async (refresh) => {
64
+ * const { data } = await Auth.tokenRefreshCreate({ body: { refresh } });
65
+ * return data ? { access: data.access, refresh: data.refresh } : null;
66
+ * });
67
+ */
68
+ setRefreshHandler(fn: RefreshHandler | null): void;
39
69
  };
40
70
 
41
71
  interface RequestLog {
@@ -123,6 +153,13 @@ declare class API$2 {
123
153
  setLocale(locale: string | null): void;
124
154
  getApiKey(): string | null;
125
155
  setApiKey(key: string | null): void;
156
+ /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
157
+ onUnauthorized(cb: ((response: Response) => void) | null): void;
158
+ /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
159
+ setRefreshHandler(fn: ((refreshToken: string) => Promise<{
160
+ access: string;
161
+ refresh?: string;
162
+ } | null>) | null): void;
126
163
  }
127
164
 
128
165
  interface StorageAdapter {
@@ -1386,6 +1423,13 @@ declare class API$1 {
1386
1423
  setLocale(locale: string | null): void;
1387
1424
  getApiKey(): string | null;
1388
1425
  setApiKey(key: string | null): void;
1426
+ /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
1427
+ onUnauthorized(cb: ((response: Response) => void) | null): void;
1428
+ /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
1429
+ setRefreshHandler(fn: ((refreshToken: string) => Promise<{
1430
+ access: string;
1431
+ refresh?: string;
1432
+ } | null>) | null): void;
1389
1433
  }
1390
1434
 
1391
1435
  interface APIOptions {
@@ -1423,6 +1467,13 @@ declare class API {
1423
1467
  setLocale(locale: string | null): void;
1424
1468
  getApiKey(): string | null;
1425
1469
  setApiKey(key: string | null): void;
1470
+ /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
1471
+ onUnauthorized(cb: ((response: Response) => void) | null): void;
1472
+ /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
1473
+ setRefreshHandler(fn: ((refreshToken: string) => Promise<{
1474
+ access: string;
1475
+ refresh?: string;
1476
+ } | null>) | null): void;
1426
1477
  }
1427
1478
 
1428
1479
  declare const CfgAccountsApi: API$2;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,16 @@ import { ConsolaInstance } from 'consola';
2
2
  import { ZodError } from 'zod';
3
3
 
4
4
  type StorageMode = 'localStorage' | 'cookie';
5
+ /**
6
+ * User-supplied refresh handler. Receives the current refresh token,
7
+ * must return a fresh access (and optional refresh) pair or null on failure.
8
+ * Set once at app bootstrap via `auth.setRefreshHandler(...)`.
9
+ */
10
+ type RefreshResult = {
11
+ access: string;
12
+ refresh?: string;
13
+ } | null;
14
+ type RefreshHandler = (refreshToken: string) => Promise<RefreshResult>;
5
15
  /**
6
16
  * Global auth/config store. All getters read live state every call —
7
17
  * the interceptor below uses these to attach headers per-request.
@@ -35,7 +45,27 @@ declare const auth: {
35
45
  setBaseUrl(url: string | null): void;
36
46
  getWithCredentials(): boolean;
37
47
  setWithCredentials(value: boolean): void;
48
+ /**
49
+ * Fired when the server returns 401 AND no refresh path recovers it
50
+ * (no refresh token, no refresh handler, refresh failed, or retry
51
+ * still 401). The app should clear local state and redirect to login.
52
+ *
53
+ * NOT fired for 401 that gets transparently recovered by the refresh
54
+ * handler — those are invisible to callers.
55
+ */
38
56
  onUnauthorized(cb: ((response: Response) => void) | null): void;
57
+ /**
58
+ * Register the refresh strategy. The handler receives the current
59
+ * refresh token and must call your refresh endpoint, returning
60
+ * `{ access, refresh? }` on success or `null` on failure.
61
+ *
62
+ * @example
63
+ * auth.setRefreshHandler(async (refresh) => {
64
+ * const { data } = await Auth.tokenRefreshCreate({ body: { refresh } });
65
+ * return data ? { access: data.access, refresh: data.refresh } : null;
66
+ * });
67
+ */
68
+ setRefreshHandler(fn: RefreshHandler | null): void;
39
69
  };
40
70
 
41
71
  interface RequestLog {
@@ -123,6 +153,13 @@ declare class API$2 {
123
153
  setLocale(locale: string | null): void;
124
154
  getApiKey(): string | null;
125
155
  setApiKey(key: string | null): void;
156
+ /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
157
+ onUnauthorized(cb: ((response: Response) => void) | null): void;
158
+ /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
159
+ setRefreshHandler(fn: ((refreshToken: string) => Promise<{
160
+ access: string;
161
+ refresh?: string;
162
+ } | null>) | null): void;
126
163
  }
127
164
 
128
165
  interface StorageAdapter {
@@ -1386,6 +1423,13 @@ declare class API$1 {
1386
1423
  setLocale(locale: string | null): void;
1387
1424
  getApiKey(): string | null;
1388
1425
  setApiKey(key: string | null): void;
1426
+ /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
1427
+ onUnauthorized(cb: ((response: Response) => void) | null): void;
1428
+ /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
1429
+ setRefreshHandler(fn: ((refreshToken: string) => Promise<{
1430
+ access: string;
1431
+ refresh?: string;
1432
+ } | null>) | null): void;
1389
1433
  }
1390
1434
 
1391
1435
  interface APIOptions {
@@ -1423,6 +1467,13 @@ declare class API {
1423
1467
  setLocale(locale: string | null): void;
1424
1468
  getApiKey(): string | null;
1425
1469
  setApiKey(key: string | null): void;
1470
+ /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
1471
+ onUnauthorized(cb: ((response: Response) => void) | null): void;
1472
+ /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
1473
+ setRefreshHandler(fn: ((refreshToken: string) => Promise<{
1474
+ access: string;
1475
+ refresh?: string;
1476
+ } | null>) | null): void;
1426
1477
  }
1427
1478
 
1428
1479
  declare const CfgAccountsApi: API$2;
package/dist/index.mjs CHANGED
@@ -93,6 +93,9 @@ var _apiKeyOverride = null;
93
93
  var _baseUrlOverride = null;
94
94
  var _withCredentials = true;
95
95
  var _onUnauthorized = null;
96
+ var _refreshHandler = null;
97
+ var _refreshInflight = null;
98
+ var RETRY_MARKER = "X-Auth-Retry";
96
99
  var _client = null;
97
100
  function pushClientConfig() {
98
101
  if (!_client) return;
@@ -171,10 +174,53 @@ var auth = {
171
174
  pushClientConfig();
172
175
  },
173
176
  // ── 401 handler ───────────────────────────────────────────────────
177
+ /**
178
+ * Fired when the server returns 401 AND no refresh path recovers it
179
+ * (no refresh token, no refresh handler, refresh failed, or retry
180
+ * still 401). The app should clear local state and redirect to login.
181
+ *
182
+ * NOT fired for 401 that gets transparently recovered by the refresh
183
+ * handler — those are invisible to callers.
184
+ */
174
185
  onUnauthorized(cb) {
175
186
  _onUnauthorized = cb;
187
+ },
188
+ /**
189
+ * Register the refresh strategy. The handler receives the current
190
+ * refresh token and must call your refresh endpoint, returning
191
+ * `{ access, refresh? }` on success or `null` on failure.
192
+ *
193
+ * @example
194
+ * auth.setRefreshHandler(async (refresh) => {
195
+ * const { data } = await Auth.tokenRefreshCreate({ body: { refresh } });
196
+ * return data ? { access: data.access, refresh: data.refresh } : null;
197
+ * });
198
+ */
199
+ setRefreshHandler(fn) {
200
+ _refreshHandler = fn;
176
201
  }
177
202
  };
203
+ async function tryRefresh() {
204
+ if (_refreshInflight) return _refreshInflight;
205
+ if (!_refreshHandler) return null;
206
+ const refresh = auth.getRefreshToken();
207
+ if (!refresh) return null;
208
+ _refreshInflight = (async () => {
209
+ try {
210
+ const result = await _refreshHandler(refresh);
211
+ if (!result?.access) return null;
212
+ auth.setToken(result.access);
213
+ if (result.refresh) auth.setRefreshToken(result.refresh);
214
+ return result.access;
215
+ } catch {
216
+ return null;
217
+ } finally {
218
+ _refreshInflight = null;
219
+ }
220
+ })();
221
+ return _refreshInflight;
222
+ }
223
+ __name(tryRefresh, "tryRefresh");
178
224
  function installAuthOnClient(client2) {
179
225
  if (_client) return;
180
226
  _client = client2;
@@ -191,14 +237,48 @@ function installAuthOnClient(client2) {
191
237
  if (apiKey) request.headers.set("X-API-Key", apiKey);
192
238
  return request;
193
239
  });
194
- client2.interceptors.response.use((response) => {
195
- if (response.status === 401 && _onUnauthorized) {
196
- try {
197
- _onUnauthorized(response);
198
- } catch {
240
+ client2.interceptors.response.use(async (response, request) => {
241
+ if (response.status !== 401) return response;
242
+ if (request.headers.get(RETRY_MARKER)) {
243
+ if (_onUnauthorized) {
244
+ try {
245
+ _onUnauthorized(response);
246
+ } catch {
247
+ }
248
+ }
249
+ return response;
250
+ }
251
+ const newToken = await tryRefresh();
252
+ if (!newToken) {
253
+ if (_onUnauthorized) {
254
+ try {
255
+ _onUnauthorized(response);
256
+ } catch {
257
+ }
258
+ }
259
+ return response;
260
+ }
261
+ const retry = request.clone();
262
+ retry.headers.set("Authorization", `Bearer ${newToken}`);
263
+ retry.headers.set(RETRY_MARKER, "1");
264
+ try {
265
+ const retried = await fetch(retry);
266
+ if (retried.status === 401 && _onUnauthorized) {
267
+ try {
268
+ _onUnauthorized(retried);
269
+ } catch {
270
+ }
271
+ }
272
+ return retried;
273
+ } catch {
274
+ if (_onUnauthorized) {
275
+ try {
276
+ _onUnauthorized(response);
277
+ } catch {
278
+ }
199
279
  }
280
+ return response;
200
281
  }
201
- return response;
202
282
  });
203
283
  }
204
284
  __name(installAuthOnClient, "installAuthOnClient");
@@ -349,6 +429,15 @@ var API = class {
349
429
  setApiKey(key) {
350
430
  auth.setApiKey(key);
351
431
  }
432
+ // ── 401 handling ────────────────────────────────────────────────────────
433
+ /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
434
+ onUnauthorized(cb) {
435
+ auth.onUnauthorized(cb);
436
+ }
437
+ /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
438
+ setRefreshHandler(fn) {
439
+ auth.setRefreshHandler(fn);
440
+ }
352
441
  };
353
442
 
354
443
  // src/_api/generated/helpers/storage.ts
@@ -601,6 +690,15 @@ var API2 = class {
601
690
  setApiKey(key) {
602
691
  auth.setApiKey(key);
603
692
  }
693
+ // ── 401 handling ────────────────────────────────────────────────────────
694
+ /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
695
+ onUnauthorized(cb) {
696
+ auth.onUnauthorized(cb);
697
+ }
698
+ /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
699
+ setRefreshHandler(fn) {
700
+ auth.setRefreshHandler(fn);
701
+ }
604
702
  };
605
703
 
606
704
  // src/_api/generated/_cfg_totp/api.ts
@@ -655,6 +753,15 @@ var API3 = class {
655
753
  setApiKey(key) {
656
754
  auth.setApiKey(key);
657
755
  }
756
+ // ── 401 handling ────────────────────────────────────────────────────────
757
+ /** Fired only on terminal 401 (after refresh+retry path is exhausted). */
758
+ onUnauthorized(cb) {
759
+ auth.onUnauthorized(cb);
760
+ }
761
+ /** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
762
+ setRefreshHandler(fn) {
763
+ auth.setRefreshHandler(fn);
764
+ }
658
765
  };
659
766
 
660
767
  // src/_api/generated/index.ts