@djangocfg/api 2.1.565 → 2.1.566

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/api",
3
- "version": "2.1.565",
3
+ "version": "2.1.566",
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",
@@ -85,7 +85,7 @@
85
85
  "zod": "^4.3.6"
86
86
  },
87
87
  "devDependencies": {
88
- "@djangocfg/typescript-config": "^2.1.565",
88
+ "@djangocfg/typescript-config": "^2.1.566",
89
89
  "@testing-library/react": "^16.3.2",
90
90
  "@types/node": "^25.9.5",
91
91
  "@types/react": "19.2.15",
@@ -5,8 +5,8 @@
5
5
  import { z } from "zod";
6
6
 
7
7
  export const OAuthAuthorizeRequestRequestSchema = z.object({
8
- redirect_uri: z.string().optional(),
9
- source_url: z.string().optional(),
8
+ redirect_uri: z.url().optional(),
9
+ source_url: z.url().optional(),
10
10
  });
11
11
 
12
12
  export type OAuthAuthorizeRequestRequest = z.infer<typeof OAuthAuthorizeRequestRequestSchema>;
@@ -5,7 +5,7 @@
5
5
  import { z } from "zod";
6
6
 
7
7
  export const OAuthAuthorizeResponseSchema = z.object({
8
- authorization_url: z.string(),
8
+ authorization_url: z.url(),
9
9
  state: z.string(),
10
10
  });
11
11
 
@@ -7,7 +7,7 @@ import { z } from "zod";
7
7
  export const OAuthCallbackRequestRequestSchema = z.object({
8
8
  code: z.string().min(10).max(500),
9
9
  state: z.string().min(20).max(100),
10
- redirect_uri: z.string().optional(),
10
+ redirect_uri: z.url().optional(),
11
11
  remember_me: z.boolean().default(false).optional(),
12
12
  });
13
13
 
@@ -11,7 +11,7 @@ export const OAuthConnectionSchema = z.object({
11
11
  provider_display: z.string(),
12
12
  provider_username: z.string(),
13
13
  provider_email: z.email(),
14
- provider_avatar_url: z.string(),
14
+ provider_avatar_url: z.url(),
15
15
  connected_at: z.string().datetime({ offset: true }),
16
16
  last_login_at: z.string().datetime({ offset: true }),
17
17
  });
@@ -6,7 +6,7 @@ import { z } from "zod";
6
6
 
7
7
  export const OTPRequestRequestSchema = z.object({
8
8
  identifier: z.string().min(1),
9
- source_url: z.string().optional(),
9
+ source_url: z.url().optional(),
10
10
  marketing_consent: z.boolean().nullable().optional(),
11
11
  consent_disclosure_version: z.string().max(64).optional(),
12
12
  });
@@ -7,7 +7,7 @@ import { z } from "zod";
7
7
  export const OTPVerifyRequestSchema = z.object({
8
8
  identifier: z.string().min(1),
9
9
  otp: z.string().min(4).max(4),
10
- source_url: z.string().optional(),
10
+ source_url: z.url().optional(),
11
11
  remember_me: z.boolean().default(false).optional(),
12
12
  });
13
13
 
@@ -17,7 +17,7 @@ export const UserSchema = z.object({
17
17
  position: z.string().max(100).nullable().optional(),
18
18
  language: z.string().max(10).nullable().optional(),
19
19
  timezone: z.string().max(64).nullable().optional(),
20
- avatar: z.string().nullable(),
20
+ avatar: z.url().nullable(),
21
21
  is_staff: z.boolean(),
22
22
  is_superuser: z.boolean(),
23
23
  date_joined: z.string().datetime({ offset: true }),
@@ -8,7 +8,7 @@ import { WebmailLinkProviderEnumSchema } from "./WebmailLinkProviderEnum";
8
8
  export const WebmailLinkSchema = z.object({
9
9
  provider: WebmailLinkProviderEnumSchema,
10
10
  provider_name: z.string(),
11
- url: z.string(),
11
+ url: z.url(),
12
12
  is_search: z.boolean(),
13
13
  });
14
14
 
@@ -17,7 +17,7 @@ export const TotpVerifyUserSchema = z.object({
17
17
  position: z.string().max(100).nullable().optional(),
18
18
  language: z.string().max(10).nullable().optional(),
19
19
  timezone: z.string().max(64).nullable().optional(),
20
- avatar: z.string().nullable(),
20
+ avatar: z.url().nullable(),
21
21
  is_staff: z.boolean(),
22
22
  is_superuser: z.boolean(),
23
23
  date_joined: z.string().datetime({ offset: true }),
@@ -105,11 +105,24 @@ let _storage: KVStore = backendFor(_storageMode);
105
105
  /** Detect locale from `NEXT_LOCALE` cookie or `navigator.language`. */
106
106
  function detectLocale(): string | null {
107
107
  try {
108
- if (typeof document !== 'undefined') {
109
- const m = document.cookie.match(/(?:^|;\s*)NEXT_LOCALE=([^;]*)/);
110
- const locale = m?.[1];
111
- if (locale !== undefined) return decodeURIComponent(locale);
112
- }
108
+ // BROWSER ONLY, and `document` is what decides that.
109
+ //
110
+ // `navigator` used to be the browser test and no longer is: Node 18+
111
+ // defines `navigator.language`, so on a server this returned the HOST's
112
+ // locale — `en-US` on most machines — and every server-rendered request
113
+ // carried `Accept-Language: en-US` no matter which locale the page was
114
+ // being rendered for. A valid header stating the wrong thing, which is
115
+ // worse than none: nothing looks broken.
116
+ //
117
+ // A server caller that knows its locale passes it explicitly with
118
+ // `setLocale()`. There is nothing here to detect it FROM — the request is
119
+ // the framework's, not this module's.
120
+ if (typeof document === 'undefined') return null;
121
+
122
+ const m = document.cookie.match(/(?:^|;\s*)NEXT_LOCALE=([^;]*)/);
123
+ const locale = m?.[1];
124
+ if (locale !== undefined) return decodeURIComponent(locale);
125
+
113
126
  if (typeof navigator !== 'undefined' && navigator.language) {
114
127
  return navigator.language;
115
128
  }
@@ -300,9 +313,37 @@ function ensureStorageSync(): void {
300
313
  * Terminal-401 subscribers. Unlike the single `onUnauthorized` slot these
301
314
  * compose: each registration returns its own unsubscribe, so StrictMode
302
315
  * double-mounts and multiple packages can't clobber each other.
316
+ *
317
+ * THE SET LIVES ON `globalThis`, and it has to.
318
+ *
319
+ * An app that generates its own SDK gets its own compiled copy of this file,
320
+ * while `AuthProvider` comes from `@djangocfg/api/dist` — a second copy, with
321
+ * its own module scope. Tokens survive that split because both copies read the
322
+ * same storage keys and wake each other through `SESSION_SYNC_EVENT`; a plain
323
+ * module-level `Set` does not. So the app's copy raised the terminal 401 into
324
+ * an empty set while the provider sat subscribed to the other one, and the
325
+ * redirect `AuthContext` calls "the ONE exit" never fired: the cabinet kept
326
+ * polling a dead session and the customer watched the console fill with 401s
327
+ * instead of being sent to sign in. Carapis reported ten of them.
328
+ *
329
+ * Keyed by `ACCESS_KEY` for the same reason `SESSION_SYNC_EVENT` is: two
330
+ * stores configured against different token keys are different sessions and
331
+ * must not share subscribers.
303
332
  */
304
333
  type SessionExpiredHandler = (response: Response) => void;
305
- const _sessionExpiredHandlers = new Set<SessionExpiredHandler>();
334
+
335
+ const SESSION_EXPIRED_REGISTRY = `cfg-auth:expired-handlers:${ACCESS_KEY}`;
336
+
337
+ const _sessionExpiredHandlers: Set<SessionExpiredHandler> = (() => {
338
+ // No browser guard: Node/SSR has a `globalThis` too, the registry is
339
+ // per-realm either way, and a server render simply never fires.
340
+ const g = globalThis as Record<string, unknown>;
341
+ const existing = g[SESSION_EXPIRED_REGISTRY];
342
+ if (existing instanceof Set) return existing as Set<SessionExpiredHandler>;
343
+ const created = new Set<SessionExpiredHandler>();
344
+ g[SESSION_EXPIRED_REGISTRY] = created;
345
+ return created;
346
+ })();
306
347
 
307
348
  /**
308
349
  * Captured reference to the shared Hey API client. Set exactly once by