@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/auth-server.cjs +113 -6
- package/dist/auth-server.cjs.map +1 -1
- package/dist/auth-server.mjs +113 -6
- package/dist/auth-server.mjs.map +1 -1
- package/dist/auth.cjs +113 -6
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.mjs +113 -6
- package/dist/auth.mjs.map +1 -1
- package/dist/clients.cjs +50 -0
- package/dist/clients.cjs.map +1 -1
- package/dist/clients.d.cts +21 -0
- package/dist/clients.d.ts +21 -0
- package/dist/clients.mjs +50 -0
- package/dist/clients.mjs.map +1 -1
- package/dist/index.cjs +113 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.mjs +113 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_api/generated/_cfg_accounts/api.ts +12 -0
- package/src/_api/generated/_cfg_centrifugo/api.ts +12 -0
- package/src/_api/generated/_cfg_totp/api.ts +12 -0
- package/src/_api/generated/helpers/auth.ts +103 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/api",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.335",
|
|
4
4
|
"description": "Auto-generated TypeScript API client with React hooks, SWR integration, and Zod validation for Django REST Framework backends",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"django",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@types/node": "^24.7.2",
|
|
81
81
|
"@types/react": "^19.1.0",
|
|
82
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
82
|
+
"@djangocfg/typescript-config": "^2.1.335",
|
|
83
83
|
"next": "^16.2.2",
|
|
84
84
|
"react": "^19.1.0",
|
|
85
85
|
"tsup": "^8.5.0",
|
|
@@ -61,6 +61,18 @@ export class API {
|
|
|
61
61
|
setLocale(locale: string | null): void { auth.setLocale(locale); }
|
|
62
62
|
getApiKey(): string | null { return auth.getApiKey(); }
|
|
63
63
|
setApiKey(key: string | null): void { auth.setApiKey(key); }
|
|
64
|
+
|
|
65
|
+
// ── 401 handling ────────────────────────────────────────────────────────
|
|
66
|
+
/** Fired only on terminal 401 (after refresh+retry path is exhausted). */
|
|
67
|
+
onUnauthorized(cb: ((response: Response) => void) | null): void {
|
|
68
|
+
auth.onUnauthorized(cb);
|
|
69
|
+
}
|
|
70
|
+
/** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
|
|
71
|
+
setRefreshHandler(
|
|
72
|
+
fn: ((refreshToken: string) => Promise<{ access: string; refresh?: string } | null>) | null,
|
|
73
|
+
): void {
|
|
74
|
+
auth.setRefreshHandler(fn);
|
|
75
|
+
}
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
export { };
|
|
@@ -61,6 +61,18 @@ export class API {
|
|
|
61
61
|
setLocale(locale: string | null): void { auth.setLocale(locale); }
|
|
62
62
|
getApiKey(): string | null { return auth.getApiKey(); }
|
|
63
63
|
setApiKey(key: string | null): void { auth.setApiKey(key); }
|
|
64
|
+
|
|
65
|
+
// ── 401 handling ────────────────────────────────────────────────────────
|
|
66
|
+
/** Fired only on terminal 401 (after refresh+retry path is exhausted). */
|
|
67
|
+
onUnauthorized(cb: ((response: Response) => void) | null): void {
|
|
68
|
+
auth.onUnauthorized(cb);
|
|
69
|
+
}
|
|
70
|
+
/** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
|
|
71
|
+
setRefreshHandler(
|
|
72
|
+
fn: ((refreshToken: string) => Promise<{ access: string; refresh?: string } | null>) | null,
|
|
73
|
+
): void {
|
|
74
|
+
auth.setRefreshHandler(fn);
|
|
75
|
+
}
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
export { };
|
|
@@ -61,6 +61,18 @@ export class API {
|
|
|
61
61
|
setLocale(locale: string | null): void { auth.setLocale(locale); }
|
|
62
62
|
getApiKey(): string | null { return auth.getApiKey(); }
|
|
63
63
|
setApiKey(key: string | null): void { auth.setApiKey(key); }
|
|
64
|
+
|
|
65
|
+
// ── 401 handling ────────────────────────────────────────────────────────
|
|
66
|
+
/** Fired only on terminal 401 (after refresh+retry path is exhausted). */
|
|
67
|
+
onUnauthorized(cb: ((response: Response) => void) | null): void {
|
|
68
|
+
auth.onUnauthorized(cb);
|
|
69
|
+
}
|
|
70
|
+
/** Provide a refresh strategy. See `auth.setRefreshHandler` for the contract. */
|
|
71
|
+
setRefreshHandler(
|
|
72
|
+
fn: ((refreshToken: string) => Promise<{ access: string; refresh?: string } | null>) | null,
|
|
73
|
+
): void {
|
|
74
|
+
auth.setRefreshHandler(fn);
|
|
75
|
+
}
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
export { };
|
|
@@ -105,6 +105,21 @@ let _baseUrlOverride: string | null = null;
|
|
|
105
105
|
let _withCredentials = true;
|
|
106
106
|
let _onUnauthorized: ((response: Response) => void) | null = null;
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* User-supplied refresh handler. Receives the current refresh token,
|
|
110
|
+
* must return a fresh access (and optional refresh) pair or null on failure.
|
|
111
|
+
* Set once at app bootstrap via `auth.setRefreshHandler(...)`.
|
|
112
|
+
*/
|
|
113
|
+
type RefreshResult = { access: string; refresh?: string } | null;
|
|
114
|
+
type RefreshHandler = (refreshToken: string) => Promise<RefreshResult>;
|
|
115
|
+
let _refreshHandler: RefreshHandler | null = null;
|
|
116
|
+
|
|
117
|
+
/** Single-flight: every concurrent 401 awaits the same refresh. */
|
|
118
|
+
let _refreshInflight: Promise<string | null> | null = null;
|
|
119
|
+
|
|
120
|
+
/** Marker header — set on retried requests so we never loop on 401. */
|
|
121
|
+
const RETRY_MARKER = 'X-Auth-Retry';
|
|
122
|
+
|
|
108
123
|
/**
|
|
109
124
|
* Captured reference to the shared Hey API client. Set exactly once by
|
|
110
125
|
* `installAuthOnClient(client)` (called from client.gen.ts). All `auth.set*`
|
|
@@ -192,11 +207,62 @@ export const auth = {
|
|
|
192
207
|
},
|
|
193
208
|
|
|
194
209
|
// ── 401 handler ───────────────────────────────────────────────────
|
|
210
|
+
/**
|
|
211
|
+
* Fired when the server returns 401 AND no refresh path recovers it
|
|
212
|
+
* (no refresh token, no refresh handler, refresh failed, or retry
|
|
213
|
+
* still 401). The app should clear local state and redirect to login.
|
|
214
|
+
*
|
|
215
|
+
* NOT fired for 401 that gets transparently recovered by the refresh
|
|
216
|
+
* handler — those are invisible to callers.
|
|
217
|
+
*/
|
|
195
218
|
onUnauthorized(cb: ((response: Response) => void) | null): void {
|
|
196
219
|
_onUnauthorized = cb;
|
|
197
220
|
},
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Register the refresh strategy. The handler receives the current
|
|
224
|
+
* refresh token and must call your refresh endpoint, returning
|
|
225
|
+
* `{ access, refresh? }` on success or `null` on failure.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* auth.setRefreshHandler(async (refresh) => {
|
|
229
|
+
* const { data } = await Auth.tokenRefreshCreate({ body: { refresh } });
|
|
230
|
+
* return data ? { access: data.access, refresh: data.refresh } : null;
|
|
231
|
+
* });
|
|
232
|
+
*/
|
|
233
|
+
setRefreshHandler(fn: RefreshHandler | null): void {
|
|
234
|
+
_refreshHandler = fn;
|
|
235
|
+
},
|
|
198
236
|
};
|
|
199
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Run the user-supplied refresh handler under single-flight, persist
|
|
240
|
+
* the new tokens, and return the fresh access token (or null on any
|
|
241
|
+
* failure path). All concurrent 401s share the same in-flight promise.
|
|
242
|
+
*/
|
|
243
|
+
async function tryRefresh(): Promise<string | null> {
|
|
244
|
+
if (_refreshInflight) return _refreshInflight;
|
|
245
|
+
if (!_refreshHandler) return null;
|
|
246
|
+
const refresh = auth.getRefreshToken();
|
|
247
|
+
if (!refresh) return null;
|
|
248
|
+
|
|
249
|
+
_refreshInflight = (async () => {
|
|
250
|
+
try {
|
|
251
|
+
const result = await _refreshHandler!(refresh);
|
|
252
|
+
if (!result?.access) return null;
|
|
253
|
+
auth.setToken(result.access);
|
|
254
|
+
if (result.refresh) auth.setRefreshToken(result.refresh);
|
|
255
|
+
return result.access;
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
} finally {
|
|
259
|
+
_refreshInflight = null;
|
|
260
|
+
}
|
|
261
|
+
})();
|
|
262
|
+
|
|
263
|
+
return _refreshInflight;
|
|
264
|
+
}
|
|
265
|
+
|
|
200
266
|
/**
|
|
201
267
|
* Wire the shared client to the global auth store. Called exactly
|
|
202
268
|
* once from `client.gen.ts` (post-processed) right after
|
|
@@ -229,11 +295,44 @@ export function installAuthOnClient(client: HeyClient): void {
|
|
|
229
295
|
return request;
|
|
230
296
|
});
|
|
231
297
|
|
|
232
|
-
client.interceptors.response.use((response) => {
|
|
233
|
-
if (response.status
|
|
234
|
-
|
|
298
|
+
client.interceptors.response.use(async (response, request) => {
|
|
299
|
+
if (response.status !== 401) return response;
|
|
300
|
+
|
|
301
|
+
// Already retried once — give up to avoid loops.
|
|
302
|
+
if (request.headers.get(RETRY_MARKER)) {
|
|
303
|
+
if (_onUnauthorized) {
|
|
304
|
+
try { _onUnauthorized(response); } catch {}
|
|
305
|
+
}
|
|
306
|
+
return response;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const newToken = await tryRefresh();
|
|
310
|
+
if (!newToken) {
|
|
311
|
+
if (_onUnauthorized) {
|
|
312
|
+
try { _onUnauthorized(response); } catch {}
|
|
313
|
+
}
|
|
314
|
+
return response;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Retry the original request once with the new token. We mutate a
|
|
318
|
+
// clone so the original Request body (already consumed by the
|
|
319
|
+
// failed call) doesn't trip "body already used".
|
|
320
|
+
const retry = request.clone();
|
|
321
|
+
retry.headers.set('Authorization', `Bearer ${newToken}`);
|
|
322
|
+
retry.headers.set(RETRY_MARKER, '1');
|
|
323
|
+
try {
|
|
324
|
+
const retried = await fetch(retry);
|
|
325
|
+
if (retried.status === 401 && _onUnauthorized) {
|
|
326
|
+
try { _onUnauthorized(retried); } catch {}
|
|
327
|
+
}
|
|
328
|
+
return retried;
|
|
329
|
+
} catch {
|
|
330
|
+
// Network error on retry — surface the original 401.
|
|
331
|
+
if (_onUnauthorized) {
|
|
332
|
+
try { _onUnauthorized(response); } catch {}
|
|
333
|
+
}
|
|
334
|
+
return response;
|
|
235
335
|
}
|
|
236
|
-
return response;
|
|
237
336
|
});
|
|
238
337
|
}
|
|
239
338
|
|