@djangocfg/api 2.1.591 → 2.1.593
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.cjs +214 -154
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +12 -1
- package/dist/auth.d.ts +12 -1
- package/dist/auth.mjs +137 -77
- package/dist/auth.mjs.map +1 -1
- package/dist/clients.cjs +19 -5
- package/dist/clients.cjs.map +1 -1
- package/dist/clients.mjs +19 -5
- package/dist/clients.mjs.map +1 -1
- package/dist/hooks.cjs +19 -5
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.mjs +19 -5
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.cjs +19 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +19 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_api/generated/helpers/auth.ts +17 -4
- package/src/auth/__tests__/timezone.test.ts +18 -0
- package/src/auth/hooks/index.ts +3 -0
- package/src/auth/hooks/useTimezone.ts +67 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/api",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.593",
|
|
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",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"zod": "^4.3.6"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
94
|
+
"@djangocfg/typescript-config": "^2.1.593",
|
|
95
95
|
"@testing-library/react": "^16.3.2",
|
|
96
96
|
"@types/node": "^25.9.5",
|
|
97
97
|
"@types/react": "19.2.15",
|
|
@@ -123,6 +123,15 @@ function defaultBaseUrl(): string {
|
|
|
123
123
|
return '';
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/** The browser's IANA zone; null where Intl cannot tell (e.g. a bare server runtime). */
|
|
127
|
+
function detectTimezone(): string | null {
|
|
128
|
+
try {
|
|
129
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || null;
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
126
135
|
/** Default API key fallback from `NEXT_PUBLIC_API_KEY`.
|
|
127
136
|
*
|
|
128
137
|
* Both browser and server: if NEXT_PUBLIC_API_KEY is set it is used as a
|
|
@@ -140,6 +149,7 @@ function defaultApiKey(): string | null {
|
|
|
140
149
|
|
|
141
150
|
// ── In-memory overrides (win over storage / env) ───────────────────────────
|
|
142
151
|
let _localeOverride: string | null = null;
|
|
152
|
+
let _timezoneOverride: string | null = null;
|
|
143
153
|
let _apiKeyOverride: string | null = null;
|
|
144
154
|
let _baseUrlOverride: string | null = null;
|
|
145
155
|
let _withCredentials = true;
|
|
@@ -450,6 +460,11 @@ export const auth = {
|
|
|
450
460
|
getLocale(): string | null { return _localeOverride ?? detectLocale(); },
|
|
451
461
|
setLocale(locale: string | null): void { _localeOverride = locale; },
|
|
452
462
|
|
|
463
|
+
// ── Time zone ─────────────────────────────────────────────────────
|
|
464
|
+
// IANA name sent as `X-Timezone`. `null` override = follow the browser.
|
|
465
|
+
getTimezone(): string | null { return _timezoneOverride ?? detectTimezone(); },
|
|
466
|
+
setTimezone(timezone: string | null): void { _timezoneOverride = timezone; },
|
|
467
|
+
|
|
453
468
|
// ── Base URL ──────────────────────────────────────────────────────
|
|
454
469
|
getBaseUrl(): string {
|
|
455
470
|
const url = (_baseUrlOverride ?? defaultBaseUrl());
|
|
@@ -759,10 +774,8 @@ export function installAuthOnClient(client: HeyClient): void {
|
|
|
759
774
|
const apiKey = auth.getApiKey();
|
|
760
775
|
if (apiKey && !request.headers.has('X-API-Key')) request.headers.set('X-API-Key', apiKey);
|
|
761
776
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
if (tz) request.headers.set('X-Timezone', tz);
|
|
765
|
-
} catch {}
|
|
777
|
+
const tz = auth.getTimezone();
|
|
778
|
+
if (tz) request.headers.set('X-Timezone', tz);
|
|
766
779
|
request.headers.set('X-Client-Time', new Date().toISOString());
|
|
767
780
|
|
|
768
781
|
// DPoP proof — sign a fresh per-request proof with the non-extractable key.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { auth } from '../../_api/generated/helpers/auth';
|
|
4
|
+
|
|
5
|
+
describe('auth time zone override', () => {
|
|
6
|
+
beforeEach(() => auth.setTimezone(null));
|
|
7
|
+
|
|
8
|
+
it('follows the browser until overridden', () => {
|
|
9
|
+
expect(auth.getTimezone()).toBe(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('returns the override and clears back to the browser zone', () => {
|
|
13
|
+
auth.setTimezone('Asia/Seoul');
|
|
14
|
+
expect(auth.getTimezone()).toBe('Asia/Seoul');
|
|
15
|
+
auth.setTimezone(null);
|
|
16
|
+
expect(auth.getTimezone()).toBe(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
17
|
+
});
|
|
18
|
+
});
|
package/src/auth/hooks/index.ts
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
// Session (reactive snapshot from the generated auth store)
|
|
4
4
|
export { useSession, type SessionSnapshot, type SessionStatus } from './useSession';
|
|
5
5
|
|
|
6
|
+
// Time zone preference (X-Timezone override)
|
|
7
|
+
export { useTimezone, type UseTimezoneReturn } from './useTimezone';
|
|
8
|
+
|
|
6
9
|
// Router hooks
|
|
7
10
|
export { useCfgRouter } from './useCfgRouter';
|
|
8
11
|
export { useQueryParams } from './useQueryParams';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useSyncExternalStore } from 'react';
|
|
4
|
+
|
|
5
|
+
import { auth } from '../../_api/generated/helpers/auth';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Time zone preference. The choice lives on the device (localStorage) and is
|
|
9
|
+
* pushed into the generated auth store, which sends it as `X-Timezone` on every
|
|
10
|
+
* request; Django's TimezoneMiddleware then activates it and mirrors it onto
|
|
11
|
+
* the profile. `null` = automatic (follow the browser).
|
|
12
|
+
*/
|
|
13
|
+
const STORAGE_KEY = 'djangocfg.timezone';
|
|
14
|
+
|
|
15
|
+
const listeners = new Set<() => void>();
|
|
16
|
+
|
|
17
|
+
function read(): string | null {
|
|
18
|
+
try {
|
|
19
|
+
return localStorage.getItem(STORAGE_KEY) || null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Apply the stored choice to the transport before any request goes out. */
|
|
26
|
+
function hydrate(): void {
|
|
27
|
+
if (typeof window === 'undefined') return;
|
|
28
|
+
auth.setTimezone(read());
|
|
29
|
+
}
|
|
30
|
+
hydrate();
|
|
31
|
+
|
|
32
|
+
function write(timezone: string | null): void {
|
|
33
|
+
try {
|
|
34
|
+
if (timezone) localStorage.setItem(STORAGE_KEY, timezone);
|
|
35
|
+
else localStorage.removeItem(STORAGE_KEY);
|
|
36
|
+
} catch {
|
|
37
|
+
/* storage unavailable — the choice then lasts for this page only */
|
|
38
|
+
}
|
|
39
|
+
auth.setTimezone(timezone);
|
|
40
|
+
listeners.forEach((l) => l());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function subscribe(listener: () => void): () => void {
|
|
44
|
+
listeners.add(listener);
|
|
45
|
+
return () => listeners.delete(listener);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface UseTimezoneReturn {
|
|
49
|
+
/** Zone in effect: the explicit choice, else the browser's. `null` only where Intl cannot tell. */
|
|
50
|
+
timezone: string | null;
|
|
51
|
+
/** The explicit choice; `null` = automatic. Feed this to `TimezoneSelect`. */
|
|
52
|
+
preference: string | null;
|
|
53
|
+
isAuto: boolean;
|
|
54
|
+
/** Pick a zone, or `null` for automatic. */
|
|
55
|
+
setTimezone: (timezone: string | null) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function useTimezone(): UseTimezoneReturn {
|
|
59
|
+
const preference = useSyncExternalStore(subscribe, read, () => null);
|
|
60
|
+
const setTimezone = useCallback((tz: string | null) => write(tz), []);
|
|
61
|
+
return {
|
|
62
|
+
timezone: preference ?? auth.getTimezone(),
|
|
63
|
+
preference,
|
|
64
|
+
isAuto: preference === null,
|
|
65
|
+
setTimezone,
|
|
66
|
+
};
|
|
67
|
+
}
|