@spidy092/auth-client 3.0.9 → 3.1.0
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/api.cjs +72 -4
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +72 -4
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +419 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +406 -23
- package/dist/index.js.map +1 -1
- package/dist/preferences.cjs +657 -0
- package/dist/preferences.cjs.map +1 -0
- package/dist/preferences.js +608 -0
- package/dist/preferences.js.map +1 -0
- package/dist/react/AuthProvider.cjs +99 -20
- package/dist/react/AuthProvider.cjs.map +1 -1
- package/dist/react/AuthProvider.js +99 -20
- package/dist/react/AuthProvider.js.map +1 -1
- package/dist/react/PreferencesProvider.cjs +621 -0
- package/dist/react/PreferencesProvider.cjs.map +1 -0
- package/dist/react/PreferencesProvider.js +586 -0
- package/dist/react/PreferencesProvider.js.map +1 -0
- package/dist/react/useAuth.cjs.map +1 -1
- package/dist/react/useAuth.js.map +1 -1
- package/dist/react/useSessionMonitor.cjs +343 -26
- package/dist/react/useSessionMonitor.cjs.map +1 -1
- package/dist/react/useSessionMonitor.js +342 -25
- package/dist/react/useSessionMonitor.js.map +1 -1
- package/package.json +10 -2
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// preferences.js
|
|
30
|
+
var preferences_exports = {};
|
|
31
|
+
__export(preferences_exports, {
|
|
32
|
+
PREFERENCE_DEFAULTS: () => PREFERENCE_DEFAULTS,
|
|
33
|
+
applyPreferences: () => applyPreferences,
|
|
34
|
+
clearPreferenceCache: () => clearPreferenceCache,
|
|
35
|
+
extractResponseData: () => extractResponseData,
|
|
36
|
+
formatUserDate: () => formatUserDate,
|
|
37
|
+
formatUserNumber: () => formatUserNumber,
|
|
38
|
+
formatUserRelativeTime: () => formatUserRelativeTime,
|
|
39
|
+
getPreferences: () => getPreferences,
|
|
40
|
+
normalizePreferences: () => normalizePreferences,
|
|
41
|
+
preferences: () => preferences,
|
|
42
|
+
resolveTheme: () => resolveTheme,
|
|
43
|
+
startPreferenceSync: () => startPreferenceSync,
|
|
44
|
+
subscribePreferences: () => subscribePreferences,
|
|
45
|
+
updatePreferences: () => updatePreferences
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(preferences_exports);
|
|
48
|
+
|
|
49
|
+
// api.js
|
|
50
|
+
var import_axios = __toESM(require("axios"), 1);
|
|
51
|
+
|
|
52
|
+
// token.js
|
|
53
|
+
var import_jwt_decode = require("jwt-decode");
|
|
54
|
+
var accessToken = null;
|
|
55
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
56
|
+
var REFRESH_COOKIE = "account_refresh_token";
|
|
57
|
+
var COOKIE_MAX_AGE = 7 * 24 * 60 * 60;
|
|
58
|
+
function secureAttribute() {
|
|
59
|
+
var _a;
|
|
60
|
+
try {
|
|
61
|
+
return typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.protocol) === "https:" ? "; Secure" : "";
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function writeAccessToken(token) {
|
|
67
|
+
if (!token) {
|
|
68
|
+
try {
|
|
69
|
+
localStorage.removeItem("authToken");
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.warn("Could not clear token from localStorage:", err);
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
localStorage.setItem("authToken", token);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.warn("Could not persist token to localStorage:", err);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function readAccessToken() {
|
|
82
|
+
try {
|
|
83
|
+
return localStorage.getItem("authToken");
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.warn("Could not read token from localStorage:", err);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function setToken(token) {
|
|
90
|
+
const previousToken = accessToken;
|
|
91
|
+
accessToken = token || null;
|
|
92
|
+
writeAccessToken(accessToken);
|
|
93
|
+
if (previousToken !== accessToken) {
|
|
94
|
+
listeners.forEach((listener) => {
|
|
95
|
+
try {
|
|
96
|
+
listener(accessToken, previousToken);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.warn("Token listener error:", err);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function getToken() {
|
|
104
|
+
if (accessToken) return accessToken;
|
|
105
|
+
accessToken = readAccessToken();
|
|
106
|
+
return accessToken;
|
|
107
|
+
}
|
|
108
|
+
function clearToken() {
|
|
109
|
+
if (!accessToken) {
|
|
110
|
+
writeAccessToken(null);
|
|
111
|
+
clearRefreshToken();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const previousToken = accessToken;
|
|
115
|
+
accessToken = null;
|
|
116
|
+
writeAccessToken(null);
|
|
117
|
+
clearRefreshToken();
|
|
118
|
+
listeners.forEach((listener) => {
|
|
119
|
+
try {
|
|
120
|
+
listener(null, previousToken);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.warn("Token listener error:", err);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
var REFRESH_TOKEN_KEY = "auth_refresh_token";
|
|
127
|
+
var _persistRefreshToken = false;
|
|
128
|
+
function shouldUseLocalStorage() {
|
|
129
|
+
var _a;
|
|
130
|
+
if (_persistRefreshToken) return true;
|
|
131
|
+
try {
|
|
132
|
+
return typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.protocol) === "http:";
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function setRefreshToken(token) {
|
|
138
|
+
if (!token) {
|
|
139
|
+
clearRefreshToken();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (shouldUseLocalStorage()) {
|
|
143
|
+
try {
|
|
144
|
+
localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
|
145
|
+
console.log(`\u{1F4E6} Refresh token stored in localStorage (${_persistRefreshToken ? "persistence enabled" : "HTTP dev mode"})`);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.warn("Could not store refresh token:", err);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
console.log("\u{1F512} Refresh token managed by server httpOnly cookie (production mode)");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function getRefreshToken() {
|
|
154
|
+
if (shouldUseLocalStorage()) {
|
|
155
|
+
try {
|
|
156
|
+
const token = localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
157
|
+
return token;
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.warn("Could not read refresh token:", err);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
function clearRefreshToken() {
|
|
166
|
+
try {
|
|
167
|
+
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
document.cookie = `${REFRESH_COOKIE}=; Path=/; SameSite=Strict${secureAttribute()}; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.warn("Could not clear refresh token cookie:", err);
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
sessionStorage.removeItem(REFRESH_COOKIE);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (typeof window !== "undefined" && window.addEventListener) {
|
|
181
|
+
window.addEventListener("storage", (event) => {
|
|
182
|
+
if (event.key !== "authToken") return;
|
|
183
|
+
const previousToken = accessToken;
|
|
184
|
+
accessToken = event.newValue || null;
|
|
185
|
+
if (previousToken === accessToken) return;
|
|
186
|
+
listeners.forEach((listener) => {
|
|
187
|
+
try {
|
|
188
|
+
listener(accessToken, previousToken);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.warn("Token listener error:", err);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// config.js
|
|
197
|
+
var config = {
|
|
198
|
+
clientKey: null,
|
|
199
|
+
authBaseUrl: null,
|
|
200
|
+
redirectUri: null,
|
|
201
|
+
accountUiUrl: null,
|
|
202
|
+
isRouter: false,
|
|
203
|
+
// ✅ Add router flag
|
|
204
|
+
// ========== SESSION SECURITY SETTINGS ==========
|
|
205
|
+
// Buffer time (in seconds) before token expiry to trigger proactive refresh
|
|
206
|
+
// With 5-minute access tokens, refreshing 60s before expiry ensures seamless UX
|
|
207
|
+
tokenRefreshBuffer: 60,
|
|
208
|
+
// Interval (in milliseconds) for periodic session validation
|
|
209
|
+
// Validates that the session still exists in Keycloak (not deleted by admin)
|
|
210
|
+
// Default: 15 minutes (900000ms) - Increased from 2m to avoid frequent checks
|
|
211
|
+
sessionValidationInterval: 15 * 60 * 1e3,
|
|
212
|
+
// Enable/disable periodic session validation
|
|
213
|
+
// When enabled, the client will ping the server to verify session is still active
|
|
214
|
+
enableSessionValidation: true,
|
|
215
|
+
// Enable/disable proactive token refresh
|
|
216
|
+
// When enabled, tokens are refreshed before they expire (using tokenRefreshBuffer)
|
|
217
|
+
enableProactiveRefresh: true,
|
|
218
|
+
// Validate session when browser tab becomes visible again
|
|
219
|
+
// Catches session deletions that happened while the tab was inactive
|
|
220
|
+
validateOnVisibility: true,
|
|
221
|
+
// ========== REFRESH TOKEN PERSISTENCE ==========
|
|
222
|
+
// When true, stores refresh token in localStorage even on HTTPS
|
|
223
|
+
// Required for local dev with mkcert/self-signed certs where httpOnly cookies
|
|
224
|
+
// may not work reliably across origins
|
|
225
|
+
// ⚠️ In true production, set to false and rely on httpOnly cookies
|
|
226
|
+
persistRefreshToken: false
|
|
227
|
+
};
|
|
228
|
+
function getConfig() {
|
|
229
|
+
return { ...config };
|
|
230
|
+
}
|
|
231
|
+
function isRouterMode() {
|
|
232
|
+
return config.isRouter;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// diagnostics.js
|
|
236
|
+
var STORAGE_KEY = "auth_diagnostic_context";
|
|
237
|
+
var randomId = () => {
|
|
238
|
+
var _a;
|
|
239
|
+
if ((_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) return globalThis.crypto.randomUUID();
|
|
240
|
+
return `auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
241
|
+
};
|
|
242
|
+
var fingerprint = async (value) => {
|
|
243
|
+
var _a;
|
|
244
|
+
if (!value || !((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) return null;
|
|
245
|
+
const bytes = new TextEncoder().encode(String(value));
|
|
246
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
247
|
+
return [...new Uint8Array(digest)].slice(0, 6).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
248
|
+
};
|
|
249
|
+
function getDiagnosticContext() {
|
|
250
|
+
try {
|
|
251
|
+
const existing = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "null");
|
|
252
|
+
if (existing == null ? void 0 : existing.correlationId) return existing;
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
const context = { correlationId: randomId(), startedAt: Date.now() };
|
|
256
|
+
try {
|
|
257
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
|
|
258
|
+
} catch {
|
|
259
|
+
}
|
|
260
|
+
return context;
|
|
261
|
+
}
|
|
262
|
+
function diagnosticHeaders() {
|
|
263
|
+
const { correlationId } = getDiagnosticContext();
|
|
264
|
+
return { "X-Correlation-ID": correlationId, "X-Request-ID": correlationId };
|
|
265
|
+
}
|
|
266
|
+
async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
|
|
267
|
+
const context = getDiagnosticContext();
|
|
268
|
+
const safe = {
|
|
269
|
+
eventType: "auth_client_diagnostic",
|
|
270
|
+
event,
|
|
271
|
+
outcome,
|
|
272
|
+
reasonCode,
|
|
273
|
+
correlationId: context.correlationId,
|
|
274
|
+
elapsedMs: Date.now() - context.startedAt,
|
|
275
|
+
clientKey: details.clientKey || null,
|
|
276
|
+
status: details.status || null,
|
|
277
|
+
stateFingerprint: await fingerprint(details.state),
|
|
278
|
+
online: typeof navigator === "undefined" ? null : navigator.onLine
|
|
279
|
+
};
|
|
280
|
+
const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
|
|
281
|
+
console[method]("[auth-client]", safe);
|
|
282
|
+
return safe;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// core.js
|
|
286
|
+
var refreshInProgress = false;
|
|
287
|
+
var refreshPromise = null;
|
|
288
|
+
async function refreshToken() {
|
|
289
|
+
const { clientKey, authBaseUrl } = getConfig();
|
|
290
|
+
if (refreshInProgress && refreshPromise) {
|
|
291
|
+
console.log("\u{1F504} Token refresh already in progress, waiting...");
|
|
292
|
+
return refreshPromise;
|
|
293
|
+
}
|
|
294
|
+
refreshInProgress = true;
|
|
295
|
+
refreshPromise = (async () => {
|
|
296
|
+
var _a, _b, _c, _d, _e, _f;
|
|
297
|
+
try {
|
|
298
|
+
const storedRefreshToken = getRefreshToken();
|
|
299
|
+
console.log("\u{1F504} Refreshing token:", {
|
|
300
|
+
clientKey,
|
|
301
|
+
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
302
|
+
hasStoredRefreshToken: !!storedRefreshToken
|
|
303
|
+
});
|
|
304
|
+
const requestOptions = {
|
|
305
|
+
method: "POST",
|
|
306
|
+
credentials: "include",
|
|
307
|
+
// ✅ Include httpOnly cookies (for HTTPS)
|
|
308
|
+
headers: {
|
|
309
|
+
...diagnosticHeaders(),
|
|
310
|
+
"Content-Type": "application/json"
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
if (storedRefreshToken) {
|
|
314
|
+
requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
|
|
315
|
+
console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
|
|
316
|
+
}
|
|
317
|
+
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
318
|
+
if (!response.ok) {
|
|
319
|
+
const errorText = await response.text();
|
|
320
|
+
let serverCode = null;
|
|
321
|
+
try {
|
|
322
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
326
|
+
clientKey,
|
|
327
|
+
status: response.status
|
|
328
|
+
});
|
|
329
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
330
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
331
|
+
refreshError.status = response.status;
|
|
332
|
+
throw refreshError;
|
|
333
|
+
}
|
|
334
|
+
const data = await response.json();
|
|
335
|
+
const { access_token, refresh_token: new_refresh_token } = data;
|
|
336
|
+
if (!access_token) {
|
|
337
|
+
throw new Error("No access token in refresh response");
|
|
338
|
+
}
|
|
339
|
+
setToken(access_token);
|
|
340
|
+
if (new_refresh_token) {
|
|
341
|
+
setRefreshToken(new_refresh_token);
|
|
342
|
+
console.log("\u{1F504} New refresh token stored from rotation");
|
|
343
|
+
}
|
|
344
|
+
console.log("\u2705 Token refresh successful, listeners notified");
|
|
345
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
346
|
+
return access_token;
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error("\u274C Token refresh error:", err);
|
|
349
|
+
const isAuthRejection = ((_c = err.message) == null ? void 0 : _c.includes("401")) || ((_d = err.message) == null ? void 0 : _d.includes("403")) || ((_e = err.message) == null ? void 0 : _e.includes("invalid_grant")) || ((_f = err.message) == null ? void 0 : _f.includes("Refresh failed: 4"));
|
|
350
|
+
if (isAuthRejection) {
|
|
351
|
+
clearToken();
|
|
352
|
+
clearRefreshToken();
|
|
353
|
+
}
|
|
354
|
+
throw err;
|
|
355
|
+
} finally {
|
|
356
|
+
refreshInProgress = false;
|
|
357
|
+
refreshPromise = null;
|
|
358
|
+
}
|
|
359
|
+
})();
|
|
360
|
+
return refreshPromise;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// api.js
|
|
364
|
+
var api = import_axios.default.create({
|
|
365
|
+
withCredentials: true
|
|
366
|
+
});
|
|
367
|
+
api.interceptors.request.use((config2) => {
|
|
368
|
+
const runtimeConfig = getConfig();
|
|
369
|
+
if (!config2.baseURL) {
|
|
370
|
+
config2.baseURL = (runtimeConfig == null ? void 0 : runtimeConfig.authBaseUrl) || "http://auth.local.test:4000/auth";
|
|
371
|
+
}
|
|
372
|
+
if (!config2.headers) {
|
|
373
|
+
config2.headers = {};
|
|
374
|
+
}
|
|
375
|
+
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
376
|
+
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
377
|
+
}
|
|
378
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
379
|
+
const token = getToken();
|
|
380
|
+
if (token) {
|
|
381
|
+
config2.headers.Authorization = `Bearer ${token}`;
|
|
382
|
+
}
|
|
383
|
+
return config2;
|
|
384
|
+
});
|
|
385
|
+
var refreshPromise2 = null;
|
|
386
|
+
api.interceptors.response.use(
|
|
387
|
+
(response) => response,
|
|
388
|
+
async (error) => {
|
|
389
|
+
const { response, config: config2 } = error || {};
|
|
390
|
+
if (!response || !config2) {
|
|
391
|
+
return Promise.reject(error);
|
|
392
|
+
}
|
|
393
|
+
if (response.status !== 401 || config2._retry) {
|
|
394
|
+
return Promise.reject(error);
|
|
395
|
+
}
|
|
396
|
+
config2._retry = true;
|
|
397
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
398
|
+
clientKey: getConfig().clientKey,
|
|
399
|
+
status: 401
|
|
400
|
+
});
|
|
401
|
+
if (!refreshPromise2) {
|
|
402
|
+
refreshPromise2 = refreshToken().then((newToken) => {
|
|
403
|
+
refreshPromise2 = null;
|
|
404
|
+
if (newToken) {
|
|
405
|
+
setToken(newToken);
|
|
406
|
+
}
|
|
407
|
+
return newToken;
|
|
408
|
+
}).catch((refreshError) => {
|
|
409
|
+
refreshPromise2 = null;
|
|
410
|
+
throw refreshError;
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const refreshedToken = await refreshPromise2;
|
|
415
|
+
if (refreshedToken) {
|
|
416
|
+
config2.headers.Authorization = `Bearer ${refreshedToken}`;
|
|
417
|
+
return api(config2);
|
|
418
|
+
}
|
|
419
|
+
} catch (refreshErr) {
|
|
420
|
+
return Promise.reject(error);
|
|
421
|
+
}
|
|
422
|
+
return Promise.reject(error);
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
api.validateSession = async () => {
|
|
426
|
+
var _a;
|
|
427
|
+
try {
|
|
428
|
+
const response = await api.get("/account/validate-session");
|
|
429
|
+
return response.data.valid;
|
|
430
|
+
} catch (err) {
|
|
431
|
+
if (((_a = err.response) == null ? void 0 : _a.status) === 401) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
throw err;
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
var api_default = api;
|
|
438
|
+
|
|
439
|
+
// preferences.js
|
|
440
|
+
var PREFERENCE_DEFAULTS = Object.freeze({
|
|
441
|
+
theme: "system",
|
|
442
|
+
colorScheme: "blue",
|
|
443
|
+
density: "comfortable",
|
|
444
|
+
reducedMotion: false,
|
|
445
|
+
language: "en",
|
|
446
|
+
timezone: "Etc/UTC",
|
|
447
|
+
dateFormat: "MM/DD/YYYY",
|
|
448
|
+
timeFormat: "12h",
|
|
449
|
+
highContrast: false,
|
|
450
|
+
largeText: false
|
|
451
|
+
});
|
|
452
|
+
var ALLOWED = {
|
|
453
|
+
theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
|
|
454
|
+
density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
|
|
455
|
+
timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
|
|
456
|
+
};
|
|
457
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
458
|
+
var cachedPreferences = null;
|
|
459
|
+
var pendingRequest = null;
|
|
460
|
+
var syncCleanup = null;
|
|
461
|
+
function extractResponseData(response) {
|
|
462
|
+
var _a;
|
|
463
|
+
return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
|
|
464
|
+
}
|
|
465
|
+
function normalizePreferences(value = {}) {
|
|
466
|
+
const next = { ...PREFERENCE_DEFAULTS };
|
|
467
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
468
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
469
|
+
const candidate = value[key];
|
|
470
|
+
if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
|
|
471
|
+
if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
|
|
472
|
+
next[key] = candidate;
|
|
473
|
+
}
|
|
474
|
+
return next;
|
|
475
|
+
}
|
|
476
|
+
function resolveTheme(theme, matchMedia = globalThis.matchMedia) {
|
|
477
|
+
if (theme === "dark" || theme === "light") return theme;
|
|
478
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
479
|
+
}
|
|
480
|
+
function announce(preferences2) {
|
|
481
|
+
cachedPreferences = normalizePreferences(preferences2);
|
|
482
|
+
listeners2.forEach((listener) => listener(cachedPreferences));
|
|
483
|
+
return cachedPreferences;
|
|
484
|
+
}
|
|
485
|
+
async function getPreferences({ force = false } = {}) {
|
|
486
|
+
if (cachedPreferences && !force) return cachedPreferences;
|
|
487
|
+
if (pendingRequest) return pendingRequest;
|
|
488
|
+
pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
|
|
489
|
+
pendingRequest = null;
|
|
490
|
+
});
|
|
491
|
+
return pendingRequest;
|
|
492
|
+
}
|
|
493
|
+
async function updatePreferences(patch) {
|
|
494
|
+
const allowedPatch = {};
|
|
495
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
496
|
+
if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
|
|
497
|
+
}
|
|
498
|
+
const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
|
|
499
|
+
const response = await api_default.put("/account/preferences", allowedPatch);
|
|
500
|
+
const saved = extractResponseData(response);
|
|
501
|
+
return announce(Object.keys(saved).length ? saved : candidate);
|
|
502
|
+
}
|
|
503
|
+
function subscribePreferences(listener) {
|
|
504
|
+
listeners2.add(listener);
|
|
505
|
+
if (cachedPreferences) listener(cachedPreferences);
|
|
506
|
+
return () => listeners2.delete(listener);
|
|
507
|
+
}
|
|
508
|
+
function clearPreferenceCache() {
|
|
509
|
+
cachedPreferences = null;
|
|
510
|
+
pendingRequest = null;
|
|
511
|
+
}
|
|
512
|
+
function applyPreferences(preferences2, documentRef = globalThis.document) {
|
|
513
|
+
var _a, _b, _c;
|
|
514
|
+
if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
|
|
515
|
+
const value = normalizePreferences(preferences2);
|
|
516
|
+
const root = documentRef.documentElement;
|
|
517
|
+
const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
|
|
518
|
+
root.dataset.theme = resolvedTheme;
|
|
519
|
+
root.dataset.themePreference = value.theme;
|
|
520
|
+
root.dataset.colorScheme = value.colorScheme;
|
|
521
|
+
root.dataset.density = value.density;
|
|
522
|
+
root.lang = value.language;
|
|
523
|
+
root.style.colorScheme = resolvedTheme;
|
|
524
|
+
root.style.setProperty("--user-accent-color", {
|
|
525
|
+
blue: "#1976d2",
|
|
526
|
+
green: "#2e7d32",
|
|
527
|
+
purple: "#7b1fa2",
|
|
528
|
+
orange: "#c65d00",
|
|
529
|
+
red: "#c62828"
|
|
530
|
+
}[value.colorScheme] || "#1976d2");
|
|
531
|
+
root.classList.toggle("user-reduced-motion", value.reducedMotion);
|
|
532
|
+
root.classList.toggle("user-high-contrast", value.highContrast);
|
|
533
|
+
root.classList.toggle("user-large-text", value.largeText);
|
|
534
|
+
const styleId = "sso-user-preference-styles";
|
|
535
|
+
if (!documentRef.getElementById(styleId)) {
|
|
536
|
+
const style = documentRef.createElement("style");
|
|
537
|
+
style.id = styleId;
|
|
538
|
+
style.textContent = `
|
|
539
|
+
html.user-large-text { font-size: 112.5%; }
|
|
540
|
+
html.user-high-contrast { filter: contrast(1.15); }
|
|
541
|
+
html[data-density="compact"] { --user-density-padding: 6px; }
|
|
542
|
+
html[data-density="comfortable"] { --user-density-padding: 10px; }
|
|
543
|
+
html[data-density="spacious"] { --user-density-padding: 14px; }
|
|
544
|
+
html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
|
|
545
|
+
html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
|
|
546
|
+
animation-duration: 0.01ms !important;
|
|
547
|
+
animation-iteration-count: 1 !important;
|
|
548
|
+
scroll-behavior: auto !important;
|
|
549
|
+
transition-duration: 0.01ms !important;
|
|
550
|
+
}
|
|
551
|
+
`;
|
|
552
|
+
(_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function startPreferenceSync({ refreshOnFocus = true } = {}) {
|
|
556
|
+
var _a, _b;
|
|
557
|
+
if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
|
|
558
|
+
});
|
|
559
|
+
const refresh = () => {
|
|
560
|
+
if (!globalThis.document || globalThis.document.visibilityState === "visible") {
|
|
561
|
+
getPreferences({ force: true }).catch(() => {
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
const onVisibility = () => refresh();
|
|
566
|
+
(_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
|
|
567
|
+
if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
|
|
568
|
+
syncCleanup = () => {
|
|
569
|
+
var _a2, _b2;
|
|
570
|
+
(_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
|
|
571
|
+
if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
|
|
572
|
+
syncCleanup = null;
|
|
573
|
+
};
|
|
574
|
+
return syncCleanup;
|
|
575
|
+
}
|
|
576
|
+
function localeFor(language) {
|
|
577
|
+
const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
|
|
578
|
+
return supported[0] || "en";
|
|
579
|
+
}
|
|
580
|
+
function fixedOffset(value) {
|
|
581
|
+
const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
|
|
582
|
+
if (!match) return null;
|
|
583
|
+
const minutes = Number(match[2]) * 60 + Number(match[3]);
|
|
584
|
+
return (match[1] === "-" ? -1 : 1) * minutes;
|
|
585
|
+
}
|
|
586
|
+
function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
587
|
+
const value = normalizePreferences(preferences2);
|
|
588
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
589
|
+
const offset = fixedOffset(value.timezone);
|
|
590
|
+
const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
|
|
591
|
+
const timeZone = offset === null ? value.timezone : "UTC";
|
|
592
|
+
const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
|
|
593
|
+
const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
|
|
594
|
+
const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
|
|
595
|
+
...dateOptions,
|
|
596
|
+
timeZone,
|
|
597
|
+
...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
|
|
598
|
+
});
|
|
599
|
+
if (!hasExplicitParts) {
|
|
600
|
+
const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
|
|
601
|
+
if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
|
|
602
|
+
if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
|
|
603
|
+
return `${parts.month}/${parts.day}/${parts.year}`;
|
|
604
|
+
}
|
|
605
|
+
return formatter.format(adjusted);
|
|
606
|
+
}
|
|
607
|
+
function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
608
|
+
const value = normalizePreferences(preferences2);
|
|
609
|
+
return new Intl.NumberFormat(localeFor(value.language), options).format(input);
|
|
610
|
+
}
|
|
611
|
+
function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
|
|
612
|
+
const value = normalizePreferences(preferences2);
|
|
613
|
+
const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
|
|
614
|
+
const units = [
|
|
615
|
+
["year", 31536e3],
|
|
616
|
+
["month", 2592e3],
|
|
617
|
+
["week", 604800],
|
|
618
|
+
["day", 86400],
|
|
619
|
+
["hour", 3600],
|
|
620
|
+
["minute", 60],
|
|
621
|
+
["second", 1]
|
|
622
|
+
];
|
|
623
|
+
const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
|
|
624
|
+
return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
|
|
625
|
+
}
|
|
626
|
+
var preferences = {
|
|
627
|
+
defaults: PREFERENCE_DEFAULTS,
|
|
628
|
+
get: getPreferences,
|
|
629
|
+
update: updatePreferences,
|
|
630
|
+
subscribe: subscribePreferences,
|
|
631
|
+
clearCache: clearPreferenceCache,
|
|
632
|
+
apply: applyPreferences,
|
|
633
|
+
startSync: startPreferenceSync,
|
|
634
|
+
formatDate: formatUserDate,
|
|
635
|
+
formatNumber: formatUserNumber,
|
|
636
|
+
formatRelativeTime: formatUserRelativeTime,
|
|
637
|
+
normalize: normalizePreferences,
|
|
638
|
+
resolveTheme
|
|
639
|
+
};
|
|
640
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
641
|
+
0 && (module.exports = {
|
|
642
|
+
PREFERENCE_DEFAULTS,
|
|
643
|
+
applyPreferences,
|
|
644
|
+
clearPreferenceCache,
|
|
645
|
+
extractResponseData,
|
|
646
|
+
formatUserDate,
|
|
647
|
+
formatUserNumber,
|
|
648
|
+
formatUserRelativeTime,
|
|
649
|
+
getPreferences,
|
|
650
|
+
normalizePreferences,
|
|
651
|
+
preferences,
|
|
652
|
+
resolveTheme,
|
|
653
|
+
startPreferenceSync,
|
|
654
|
+
subscribePreferences,
|
|
655
|
+
updatePreferences
|
|
656
|
+
});
|
|
657
|
+
//# sourceMappingURL=preferences.cjs.map
|