@goralys/core 1.4.3 → 1.5.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/index.d.ts +62 -14
- package/dist/index.js +216 -253
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -45,11 +45,26 @@ type CheckAuthImpl = () => boolean;
|
|
|
45
45
|
declare function configureCheckAuth(impl: CheckAuthImpl): void;
|
|
46
46
|
declare function isAuthenticated(): boolean;
|
|
47
47
|
|
|
48
|
+
type Toast = {
|
|
49
|
+
type: "error" | "warning" | "info" | "success";
|
|
50
|
+
title: string;
|
|
51
|
+
message: string;
|
|
52
|
+
expires?: number;
|
|
53
|
+
};
|
|
54
|
+
type ToastFn = (toast: Toast, duration?: number) => void;
|
|
55
|
+
|
|
56
|
+
interface ToastConfig {
|
|
57
|
+
getShowToast: () => ToastFn;
|
|
58
|
+
}
|
|
59
|
+
declare function configureToast(cfg: ToastConfig): void;
|
|
60
|
+
declare function getToastConfig(): ToastConfig;
|
|
61
|
+
|
|
48
62
|
interface GoralysCoreConfig {
|
|
49
63
|
client: GoralysClientConfig;
|
|
50
64
|
storage: StorageAdapter;
|
|
51
65
|
cookies: CookiesAdapter;
|
|
52
66
|
auth: CheckAuthImpl;
|
|
67
|
+
toast: ToastConfig;
|
|
53
68
|
}
|
|
54
69
|
declare function configGoralysCore(cfg: GoralysCoreConfig): void;
|
|
55
70
|
|
|
@@ -76,14 +91,19 @@ type ConfirmOptions = {
|
|
|
76
91
|
title: string;
|
|
77
92
|
message: string;
|
|
78
93
|
};
|
|
94
|
+
type AuthTokenContext = "admin-panel" | "profile";
|
|
95
|
+
type AuthToken = {
|
|
96
|
+
username: string;
|
|
97
|
+
name: string;
|
|
98
|
+
expires: Date;
|
|
99
|
+
created: Date;
|
|
100
|
+
};
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
expires?: number;
|
|
102
|
+
declare function useAuthTokens(context: AuthTokenContext, target?: string | null): {
|
|
103
|
+
tokens: AuthToken[] | null;
|
|
104
|
+
refetch: () => Promise<undefined | void>;
|
|
105
|
+
syncKey: string;
|
|
85
106
|
};
|
|
86
|
-
type ToastFn = (toast: Toast, duration?: number) => void;
|
|
87
107
|
|
|
88
108
|
type SubjectStatus = "not_submitted" | "submitted" | "rejected" | "approved";
|
|
89
109
|
declare const SubjectStatusConfig: Record<SubjectStatus, {
|
|
@@ -121,7 +141,7 @@ type DraftModalResult = {
|
|
|
121
141
|
declare const getStatusLabel: (status: SubjectStatus) => string;
|
|
122
142
|
declare const getStatusHelper: (status: SubjectStatus, role: UserRole["role"]) => string;
|
|
123
143
|
|
|
124
|
-
declare function useSubjects(role: UserRole["role"]
|
|
144
|
+
declare function useSubjects(role: UserRole["role"]): {
|
|
125
145
|
subjects: Subject[] | null;
|
|
126
146
|
refetch: () => Promise<undefined | void>;
|
|
127
147
|
syncKey: string;
|
|
@@ -151,21 +171,40 @@ type SupportTicket = {
|
|
|
151
171
|
createdAt: PHPDateTime;
|
|
152
172
|
};
|
|
153
173
|
|
|
154
|
-
declare function useSupportTickets(
|
|
174
|
+
declare function useSupportTickets(): {
|
|
155
175
|
supportTickets: SupportTicket[] | null;
|
|
156
176
|
refetch: () => Promise<undefined | void>;
|
|
157
177
|
syncKey: string;
|
|
158
178
|
};
|
|
159
179
|
|
|
180
|
+
interface UseSyncedResourceOptions<T> {
|
|
181
|
+
name: string;
|
|
182
|
+
cacheKey: string;
|
|
183
|
+
syncKey: string;
|
|
184
|
+
fetcher: () => Promise<Response | null | undefined>;
|
|
185
|
+
parse: (data: unknown) => T | null;
|
|
186
|
+
requireAuth?: boolean;
|
|
187
|
+
guard?: () => string | null | undefined;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Shared implementation behind useSubjects / useAuthTokens: cookies sync, localStorage cache, single-flight de-duplication, and a cross-tab
|
|
191
|
+
* `cookiesOnChange` listener that re-fetches once another tab invalidates the sync flag.
|
|
192
|
+
*/
|
|
193
|
+
declare function useSyncedResource<T>({ name, cacheKey, syncKey, fetcher, parse, requireAuth, guard }: UseSyncedResourceOptions<T>): {
|
|
194
|
+
data: T | null;
|
|
195
|
+
refetch: () => Promise<undefined | void>;
|
|
196
|
+
syncKey: string;
|
|
197
|
+
};
|
|
198
|
+
|
|
160
199
|
interface UserFetchResult {
|
|
161
200
|
users: User[] | null;
|
|
162
201
|
refetch: () => Promise<void | undefined>;
|
|
163
202
|
syncKey: string;
|
|
164
203
|
}
|
|
165
|
-
declare function useUsers(
|
|
166
|
-
declare function useVirtualUsers(
|
|
167
|
-
declare function useAdmins(
|
|
168
|
-
declare function useVirtualAdmins(
|
|
204
|
+
declare function useUsers(): UserFetchResult;
|
|
205
|
+
declare function useVirtualUsers(): UserFetchResult;
|
|
206
|
+
declare function useAdmins(): UserFetchResult;
|
|
207
|
+
declare function useVirtualAdmins(): UserFetchResult;
|
|
169
208
|
|
|
170
209
|
declare const LOGO_PATH: {
|
|
171
210
|
main: string;
|
|
@@ -183,6 +222,8 @@ declare const EMAIL_KEY = "email";
|
|
|
183
222
|
declare const PUB_ID_KEY = "public-id";
|
|
184
223
|
declare const SUBJECT_SYNC_RADIX = "subjects-synced-";
|
|
185
224
|
declare const SUBJECT_CACHE_RADIX = "subjects-cache-";
|
|
225
|
+
declare const AUTH_TOKEN_SYNC_RADIX = "auth-tokens-sync-";
|
|
226
|
+
declare const AUTH_TOKEN_CACHE_RADIX = "auth-tokens-cache-";
|
|
186
227
|
declare const USER_CACHE_RADIX = "users-cache-";
|
|
187
228
|
declare const USER_SYNC_RADIX = "users-synced-";
|
|
188
229
|
declare const ADMIN_CACHE_RADIX = "admins-cache-";
|
|
@@ -191,6 +232,8 @@ declare const SUPPORT_TICKET_CACHE = "support-tickets-cache";
|
|
|
191
232
|
declare const SUPPORT_TICKET_SYNC = "support-tickets-synced";
|
|
192
233
|
declare const SUBJECT_SYNCS: Record<UserRole["role"], string>;
|
|
193
234
|
declare const SUBJECT_CACHES: Record<UserRole["role"], string>;
|
|
235
|
+
declare const AUTH_TOKEN_SYNCS: Record<AuthTokenContext, string>;
|
|
236
|
+
declare const AUTH_TOKEN_CACHES: Record<AuthTokenContext, string>;
|
|
194
237
|
type UserType = {
|
|
195
238
|
type: "users-virtual" | "users-real" | "admins-virtual" | "admins-real";
|
|
196
239
|
};
|
|
@@ -221,11 +264,16 @@ declare const helpContent: Section[];
|
|
|
221
264
|
declare function isFoolsDay(): boolean;
|
|
222
265
|
|
|
223
266
|
declare function buildArray<T>(...items: (T | false | null | undefined)[]): T[];
|
|
267
|
+
declare const fromPhpDate: (phpDate: PHPDateTime) => Date;
|
|
224
268
|
declare const parsePhpDateTime: (phpDate: PHPDateTime) => string;
|
|
225
269
|
|
|
226
270
|
declare function emitAuthEvent(event: AuthEvent): void;
|
|
227
271
|
declare function onAuthEvent(callback: (event: AuthEvent) => void): () => void;
|
|
228
272
|
|
|
273
|
+
declare function fetchTokensProfile(): Promise<Response | null>;
|
|
274
|
+
declare function fetchTokensAdminPanel(target: string): Promise<Response | null>;
|
|
275
|
+
declare function fetchAuthTokensForContext(ctx: AuthTokenContext, target?: string | null): Promise<Response | null>;
|
|
276
|
+
|
|
229
277
|
declare function emitUserEvent(event: UserEvent): void;
|
|
230
278
|
declare function onUserEvent(callback: (event: UserEvent) => void): () => void;
|
|
231
279
|
|
|
@@ -245,7 +293,7 @@ declare function isHTTPMethod(maybeMethod: string): maybeMethod is HttpMethod;
|
|
|
245
293
|
declare function goralysFetchClient(method: HttpMethod, input: string | URL | Request, payload?: Record<string, string | number | boolean | null> | FormData, requestOptions?: RequestInit): Promise<Response>;
|
|
246
294
|
declare function fetchCsrfClient(formId: string): Promise<string | null>;
|
|
247
295
|
declare function buildApiUrl(endpoint: string, params: Record<string, string | null>, domain?: boolean): string;
|
|
248
|
-
declare function handleToastRequest(r: Response, showToast:
|
|
296
|
+
declare function handleToastRequest(r: Response, showToast: ToastFn, redirect?: boolean, duration?: number): Promise<boolean>;
|
|
249
297
|
|
|
250
298
|
declare class GoralysActionHandler {
|
|
251
299
|
handle: (r: Response) => Promise<void>;
|
|
@@ -291,4 +339,4 @@ declare function fetchVirtualUsersClient(): Promise<Response>;
|
|
|
291
339
|
declare function fetchAdminsClient(): Promise<Response>;
|
|
292
340
|
declare function fetchVirtualAdminsClient(): Promise<Response>;
|
|
293
341
|
|
|
294
|
-
export { ADMIN_CACHE_RADIX, ADMIN_SYNC_RADIX, type AuthEvent, COOKIES_DISMISSED_KEY, type CheckAuthImpl, type ConfirmOptions, type CookieValue, type CookiesAdapter, type DraftModalResult, EMAIL_KEY, FLASH_TOAST_KEY, FULL_NAME_KEY, GoralysActionHandler, type GoralysClientConfig, HTTP_METHODS, type HttpMethod, LOGO_PATH, PERSISTANT_COOKIES, PERSISTANT_LOCALS, type PHPDateTime, PUB_ID_KEY, ROLE_KEY, SUBJECT_CACHES, SUBJECT_CACHE_RADIX, SUBJECT_SYNCS, SUBJECT_SYNC_RADIX, SUPPORT_TICKET_CACHE, SUPPORT_TICKET_SYNC, type StorageAdapter, type Subject, type SubjectStatus, SubjectStatusConfig, type SubjectsSearchField, type SupportReasons, type SupportTicket, type Toast, type ToastFn, USERNAME_KEY, USER_CACHES, USER_CACHE_RADIX, USER_ROLES, USER_SYNCS, USER_SYNC_RADIX, type User, type UserData, type UserEvent, type UserRole, type UserType, buildApiUrl, buildArray, cacheUserDataClient, configGoralysCore, configureCheckAuth, configureCookies, configureGoralysClient, configureStorage, cookiesGet, cookiesGetAll, cookiesOnChange, cookiesRemove, cookiesSet, emitAuthEvent, emitNavigationEvent, emitUserEvent, emptyUserCacheClient, fetchAdminsClient, fetchCsrfClient, fetchSubjectsForRoleClient, fetchUsersClient, fetchVirtualAdminsClient, fetchVirtualUsersClient, getGoralysClientConfig, getLongFromShort, getShortFromLong, getStatusHelper, getStatusLabel, goralysFetchClient, handleToastRequest, helpContent, isAuthenticated, isFoolsDay, isHTTPMethod, onAuthEvent, onNavigationEvent, onUserEvent, parsePhpDateTime, reasonsConfig, searchFields, storageGet, storageKeyAt, storageRemove, storageSet, storageSize, useAdmins, useSubjects, useSupportTickets, useUsers, useVirtualAdmins, useVirtualUsers };
|
|
342
|
+
export { ADMIN_CACHE_RADIX, ADMIN_SYNC_RADIX, AUTH_TOKEN_CACHES, AUTH_TOKEN_CACHE_RADIX, AUTH_TOKEN_SYNCS, AUTH_TOKEN_SYNC_RADIX, type AuthEvent, type AuthToken, type AuthTokenContext, COOKIES_DISMISSED_KEY, type CheckAuthImpl, type ConfirmOptions, type CookieValue, type CookiesAdapter, type DraftModalResult, EMAIL_KEY, FLASH_TOAST_KEY, FULL_NAME_KEY, GoralysActionHandler, type GoralysClientConfig, HTTP_METHODS, type HttpMethod, LOGO_PATH, PERSISTANT_COOKIES, PERSISTANT_LOCALS, type PHPDateTime, PUB_ID_KEY, ROLE_KEY, SUBJECT_CACHES, SUBJECT_CACHE_RADIX, SUBJECT_SYNCS, SUBJECT_SYNC_RADIX, SUPPORT_TICKET_CACHE, SUPPORT_TICKET_SYNC, type StorageAdapter, type Subject, type SubjectStatus, SubjectStatusConfig, type SubjectsSearchField, type SupportReasons, type SupportTicket, type Toast, type ToastConfig, type ToastFn, USERNAME_KEY, USER_CACHES, USER_CACHE_RADIX, USER_ROLES, USER_SYNCS, USER_SYNC_RADIX, type UseSyncedResourceOptions, type User, type UserData, type UserEvent, type UserRole, type UserType, buildApiUrl, buildArray, cacheUserDataClient, configGoralysCore, configureCheckAuth, configureCookies, configureGoralysClient, configureStorage, configureToast, cookiesGet, cookiesGetAll, cookiesOnChange, cookiesRemove, cookiesSet, emitAuthEvent, emitNavigationEvent, emitUserEvent, emptyUserCacheClient, fetchAdminsClient, fetchAuthTokensForContext, fetchCsrfClient, fetchSubjectsForRoleClient, fetchTokensAdminPanel, fetchTokensProfile, fetchUsersClient, fetchVirtualAdminsClient, fetchVirtualUsersClient, fromPhpDate, getGoralysClientConfig, getLongFromShort, getShortFromLong, getStatusHelper, getStatusLabel, getToastConfig, goralysFetchClient, handleToastRequest, helpContent, isAuthenticated, isFoolsDay, isHTTPMethod, onAuthEvent, onNavigationEvent, onUserEvent, parsePhpDateTime, reasonsConfig, searchFields, storageGet, storageKeyAt, storageRemove, storageSet, storageSize, useAdmins, useAuthTokens, useSubjects, useSupportTickets, useSyncedResource, useUsers, useVirtualAdmins, useVirtualUsers };
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,8 @@ function configureGoralysClient(cfg) {
|
|
|
4
4
|
config = cfg;
|
|
5
5
|
}
|
|
6
6
|
function getGoralysClientConfig() {
|
|
7
|
-
if (!config)
|
|
7
|
+
if (!config)
|
|
8
8
|
throw new Error("Goralys client not configured. Call configureGoralysClient() first.");
|
|
9
|
-
}
|
|
10
9
|
return config;
|
|
11
10
|
}
|
|
12
11
|
|
|
@@ -56,38 +55,28 @@ function isAuthenticated() {
|
|
|
56
55
|
return checkAuthImpl();
|
|
57
56
|
}
|
|
58
57
|
|
|
58
|
+
// src/lib/toast/config.ts
|
|
59
|
+
var config2 = null;
|
|
60
|
+
function configureToast(cfg) {
|
|
61
|
+
config2 = cfg;
|
|
62
|
+
}
|
|
63
|
+
function getToastConfig() {
|
|
64
|
+
if (!config2)
|
|
65
|
+
throw new Error("Toasts have not been configured yet. Call configureToast() first.");
|
|
66
|
+
return config2;
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
// src/config.ts
|
|
60
70
|
function configGoralysCore(cfg) {
|
|
61
71
|
configureGoralysClient(cfg.client);
|
|
62
72
|
configureCookies(cfg.cookies);
|
|
63
73
|
configureStorage(cfg.storage);
|
|
64
74
|
configureCheckAuth(cfg.auth);
|
|
75
|
+
configureToast(cfg.toast);
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
// src/hooks/
|
|
68
|
-
import { useCallback
|
|
69
|
-
|
|
70
|
-
// src/lib/auth/auth-event.ts
|
|
71
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
72
|
-
function emitAuthEvent(event) {
|
|
73
|
-
listeners.forEach((l) => l(event));
|
|
74
|
-
}
|
|
75
|
-
function onAuthEvent(callback) {
|
|
76
|
-
listeners.add(callback);
|
|
77
|
-
return () => {
|
|
78
|
-
listeners.delete(callback);
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// src/lib/fetch/navigation-event.ts
|
|
83
|
-
var listeners2 = /* @__PURE__ */ new Set();
|
|
84
|
-
function emitNavigationEvent(event) {
|
|
85
|
-
listeners2.forEach((l) => l(event));
|
|
86
|
-
}
|
|
87
|
-
function onNavigationEvent(listener) {
|
|
88
|
-
listeners2.add(listener);
|
|
89
|
-
return () => listeners2.delete(listener);
|
|
90
|
-
}
|
|
78
|
+
// src/hooks/use-auth-tokens.ts
|
|
79
|
+
import { useCallback as useCallback2 } from "react";
|
|
91
80
|
|
|
92
81
|
// src/lib/config.ts
|
|
93
82
|
var COOKIES_DISMISSED_KEY = "cookie-banner-dismissed";
|
|
@@ -101,6 +90,8 @@ var EMAIL_KEY = "email";
|
|
|
101
90
|
var PUB_ID_KEY = "public-id";
|
|
102
91
|
var SUBJECT_SYNC_RADIX = "subjects-synced-";
|
|
103
92
|
var SUBJECT_CACHE_RADIX = "subjects-cache-";
|
|
93
|
+
var AUTH_TOKEN_SYNC_RADIX = "auth-tokens-sync-";
|
|
94
|
+
var AUTH_TOKEN_CACHE_RADIX = "auth-tokens-cache-";
|
|
104
95
|
var USER_CACHE_RADIX = "users-cache-";
|
|
105
96
|
var USER_SYNC_RADIX = "users-synced-";
|
|
106
97
|
var ADMIN_CACHE_RADIX = "admins-cache-";
|
|
@@ -119,6 +110,14 @@ var SUBJECT_CACHES = {
|
|
|
119
110
|
admin: SUBJECT_CACHE_RADIX + "admin",
|
|
120
111
|
none: SUBJECT_CACHE_RADIX + "unknown"
|
|
121
112
|
};
|
|
113
|
+
var AUTH_TOKEN_SYNCS = {
|
|
114
|
+
"admin-panel": AUTH_TOKEN_SYNC_RADIX + "admin-panel",
|
|
115
|
+
profile: AUTH_TOKEN_SYNC_RADIX + "profile"
|
|
116
|
+
};
|
|
117
|
+
var AUTH_TOKEN_CACHES = {
|
|
118
|
+
"admin-panel": AUTH_TOKEN_CACHE_RADIX + "admin-panel",
|
|
119
|
+
profile: AUTH_TOKEN_CACHE_RADIX + "profile"
|
|
120
|
+
};
|
|
122
121
|
var USER_SYNCS = {
|
|
123
122
|
"users-real": USER_SYNC_RADIX + "users",
|
|
124
123
|
"users-virtual": USER_SYNC_RADIX + "users-virtual",
|
|
@@ -332,6 +331,28 @@ Attention : les administrateurs ont acc\xE8s \xE0 toutes les fonctionnalit\xE9s
|
|
|
332
331
|
}
|
|
333
332
|
];
|
|
334
333
|
|
|
334
|
+
// src/lib/auth/auth-event.ts
|
|
335
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
336
|
+
function emitAuthEvent(event) {
|
|
337
|
+
listeners.forEach((l) => l(event));
|
|
338
|
+
}
|
|
339
|
+
function onAuthEvent(callback) {
|
|
340
|
+
listeners.add(callback);
|
|
341
|
+
return () => {
|
|
342
|
+
listeners.delete(callback);
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/lib/fetch/navigation-event.ts
|
|
347
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
348
|
+
function emitNavigationEvent(event) {
|
|
349
|
+
listeners2.forEach((l) => l(event));
|
|
350
|
+
}
|
|
351
|
+
function onNavigationEvent(listener) {
|
|
352
|
+
listeners2.add(listener);
|
|
353
|
+
return () => listeners2.delete(listener);
|
|
354
|
+
}
|
|
355
|
+
|
|
335
356
|
// src/lib/user/user.client.ts
|
|
336
357
|
async function cacheUserDataClient() {
|
|
337
358
|
const res = await goralysFetchClient("GET", "user/profile");
|
|
@@ -414,17 +435,21 @@ var GoralysActionHandler = class {
|
|
|
414
435
|
// src/lib/fetch/fetch.client.ts
|
|
415
436
|
var actionHandler = new GoralysActionHandler();
|
|
416
437
|
async function goralysFetchClient(method, input, payload, requestOptions) {
|
|
438
|
+
if (requestOptions?.body)
|
|
439
|
+
console.error("[Fetch] Found body inside request options. This is not allowed, consider using the payload parameter instead");
|
|
417
440
|
const { apiDomain, getSchoolToken, isNative } = getGoralysClientConfig();
|
|
441
|
+
const { headers: extraHeaders, ...restOptions } = requestOptions ?? {};
|
|
418
442
|
const res = await fetch(`${apiDomain}/${input}`, {
|
|
419
443
|
credentials: "include",
|
|
420
444
|
method: method === "BREW" ? "POST" : method,
|
|
421
445
|
headers: {
|
|
422
446
|
...method === "BREW" ? { "X-HTTP-Method-Override": method } : {},
|
|
423
447
|
"X-High-School-Token": getSchoolToken(),
|
|
424
|
-
"X-Goralys-Client": isNative ? "mobile" : "desktop"
|
|
448
|
+
"X-Goralys-Client": isNative ? "mobile" : "desktop",
|
|
449
|
+
...extraHeaders
|
|
425
450
|
},
|
|
426
451
|
body: payload ? payload instanceof FormData ? payload : JSON.stringify(payload) : void 0,
|
|
427
|
-
...
|
|
452
|
+
...restOptions
|
|
428
453
|
});
|
|
429
454
|
const clone = res.clone();
|
|
430
455
|
const contentType = clone.headers.get("Content-Type");
|
|
@@ -491,56 +516,52 @@ async function handleToastRequest(r, showToast, redirect = true, duration) {
|
|
|
491
516
|
return false;
|
|
492
517
|
}
|
|
493
518
|
|
|
494
|
-
// src/lib/
|
|
495
|
-
async function
|
|
496
|
-
const
|
|
497
|
-
if (!
|
|
498
|
-
|
|
499
|
-
return await goralysFetchClient("GET", buildApiUrl("subjects/student", { "csrf-token": csrfToken }));
|
|
500
|
-
}
|
|
501
|
-
async function fetchTeacherSubjectsClient() {
|
|
502
|
-
const csrfToken = await fetchCsrfClient("get-teacher-subjects");
|
|
503
|
-
if (!csrfToken)
|
|
519
|
+
// src/lib/auth/fetch-auth-tokens.ts
|
|
520
|
+
async function fetchTokensProfile() {
|
|
521
|
+
const username = cookiesGet(USERNAME_KEY);
|
|
522
|
+
if (!username) {
|
|
523
|
+
console.error("[fetchTokensProfile] No username cookies, aborting");
|
|
504
524
|
return null;
|
|
505
|
-
|
|
525
|
+
}
|
|
526
|
+
return await goralysFetchClient("GET", "user/tokens");
|
|
506
527
|
}
|
|
507
|
-
async function
|
|
508
|
-
const
|
|
509
|
-
if (!
|
|
528
|
+
async function fetchTokensAdminPanel(target) {
|
|
529
|
+
const username = cookiesGet(USERNAME_KEY);
|
|
530
|
+
if (!username) {
|
|
531
|
+
console.error("[fetchTokensProfile] No username cookies, aborting");
|
|
510
532
|
return null;
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
return await fetchAdminSubjectsClient();
|
|
521
|
-
default:
|
|
522
|
-
return null;
|
|
533
|
+
}
|
|
534
|
+
return await goralysFetchClient("GET", "user/tokens/any", { username: target });
|
|
535
|
+
}
|
|
536
|
+
async function fetchAuthTokensForContext(ctx, target = null) {
|
|
537
|
+
switch (ctx) {
|
|
538
|
+
case "profile":
|
|
539
|
+
return await fetchTokensProfile();
|
|
540
|
+
case "admin-panel":
|
|
541
|
+
return await fetchTokensAdminPanel(target);
|
|
523
542
|
}
|
|
524
543
|
}
|
|
525
544
|
|
|
526
|
-
// src/hooks/
|
|
527
|
-
|
|
528
|
-
|
|
545
|
+
// src/hooks/use-synced-ressource.ts
|
|
546
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
547
|
+
function useSyncedResource({ name, cacheKey, syncKey, fetcher, parse, requireAuth = true, guard }) {
|
|
548
|
+
const [data, setData] = useState(null);
|
|
549
|
+
const showToast = getToastConfig().getShowToast();
|
|
529
550
|
const showToastRef = useRef(showToast);
|
|
530
551
|
useEffect(() => {
|
|
531
552
|
showToastRef.current = showToast;
|
|
532
553
|
}, [showToast]);
|
|
533
554
|
const inFlightRef = useRef(null);
|
|
534
|
-
const
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
555
|
+
const fetchResource = useCallback(async () => {
|
|
556
|
+
const abortReason = guard?.();
|
|
557
|
+
if (abortReason) {
|
|
558
|
+
console.error(`[${name}] ${abortReason}`);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (requireAuth && !isAuthenticated()) {
|
|
540
562
|
return;
|
|
541
563
|
}
|
|
542
564
|
if (inFlightRef.current) {
|
|
543
|
-
console.log("[useSubjects] in-flight, waiting");
|
|
544
565
|
return inFlightRef.current;
|
|
545
566
|
}
|
|
546
567
|
let resolve;
|
|
@@ -550,223 +571,152 @@ function useSubjects(role, showToast) {
|
|
|
550
571
|
try {
|
|
551
572
|
const syncValue = cookiesGet(syncKey);
|
|
552
573
|
if (syncValue == "1") {
|
|
553
|
-
const
|
|
554
|
-
if (
|
|
574
|
+
const raw2 = storageGet(cacheKey);
|
|
575
|
+
if (raw2 === null || raw2 === void 0) {
|
|
555
576
|
cookiesSet(syncKey, "0");
|
|
556
577
|
storageRemove(cacheKey);
|
|
557
|
-
|
|
578
|
+
inFlightRef.current = null;
|
|
579
|
+
resolve();
|
|
580
|
+
await fetchResource();
|
|
558
581
|
return;
|
|
559
582
|
}
|
|
560
|
-
const cached = JSON.parse(
|
|
561
|
-
|
|
562
|
-
if (JSON.stringify(prev) === JSON.stringify(cached))
|
|
563
|
-
return prev;
|
|
564
|
-
return cached;
|
|
565
|
-
});
|
|
583
|
+
const cached = JSON.parse(raw2 ?? "null");
|
|
584
|
+
setData((prev) => JSON.stringify(prev) === JSON.stringify(cached) ? prev : cached);
|
|
566
585
|
return;
|
|
567
586
|
}
|
|
568
|
-
const res = await
|
|
587
|
+
const res = await fetcher();
|
|
569
588
|
if (res)
|
|
570
589
|
await handleToastRequest(res, showToastRef.current, false);
|
|
571
|
-
const
|
|
590
|
+
const raw = await res?.json();
|
|
572
591
|
cookiesSet(syncKey, "1");
|
|
573
|
-
storageSet(cacheKey, JSON.stringify(
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
setSubjects((prev) => {
|
|
577
|
-
if (JSON.stringify(prev) === JSON.stringify(result))
|
|
578
|
-
return prev;
|
|
579
|
-
return result;
|
|
580
|
-
});
|
|
592
|
+
storageSet(cacheKey, JSON.stringify(raw));
|
|
593
|
+
const result = parse(raw);
|
|
594
|
+
setData((prev) => JSON.stringify(prev) === JSON.stringify(result) ? prev : result);
|
|
581
595
|
} finally {
|
|
582
596
|
inFlightRef.current = null;
|
|
583
597
|
resolve();
|
|
584
598
|
}
|
|
585
|
-
}, [
|
|
599
|
+
}, [cacheKey, syncKey, fetcher, parse, requireAuth, guard, name]);
|
|
586
600
|
useEffect(() => {
|
|
587
601
|
const onChange = () => {
|
|
588
602
|
if (inFlightRef.current)
|
|
589
603
|
return;
|
|
590
|
-
if (cookiesGet(
|
|
591
|
-
void
|
|
604
|
+
if (cookiesGet(syncKey) != "1") {
|
|
605
|
+
void fetchResource();
|
|
592
606
|
}
|
|
593
607
|
};
|
|
594
608
|
return cookiesOnChange(onChange);
|
|
595
|
-
}, [
|
|
609
|
+
}, [fetchResource, syncKey]);
|
|
596
610
|
useEffect(() => {
|
|
597
|
-
void
|
|
598
|
-
}, [
|
|
599
|
-
return useMemo(() => ({
|
|
600
|
-
subjects,
|
|
601
|
-
refetch: fetchSubjects,
|
|
602
|
-
syncKey: SUBJECT_SYNCS[role]
|
|
603
|
-
}), [subjects, fetchSubjects, role]);
|
|
611
|
+
void fetchResource();
|
|
612
|
+
}, [fetchResource]);
|
|
613
|
+
return useMemo(() => ({ data, refetch: fetchResource, syncKey }), [data, fetchResource, syncKey]);
|
|
604
614
|
}
|
|
605
615
|
|
|
606
|
-
// src/hooks/
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
if (inFlightRef.current) {
|
|
623
|
-
console.log("[useSupportTickets] in-flight, waiting");
|
|
624
|
-
return inFlightRef.current;
|
|
625
|
-
}
|
|
626
|
-
let resolve;
|
|
627
|
-
inFlightRef.current = new Promise((r) => {
|
|
628
|
-
resolve = r;
|
|
629
|
-
});
|
|
630
|
-
try {
|
|
631
|
-
const syncValue = cookiesGet(syncKey);
|
|
632
|
-
if (syncValue == "1") {
|
|
633
|
-
const raw = storageGet(cacheKey);
|
|
634
|
-
if (raw === null || raw === void 0) {
|
|
635
|
-
cookiesSet(syncKey, "0");
|
|
636
|
-
storageRemove(cacheKey);
|
|
637
|
-
await fetchSupportTickets();
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
const cached = JSON.parse(raw ?? "null");
|
|
641
|
-
setSupportTickets((prev) => {
|
|
642
|
-
if (JSON.stringify(prev) === JSON.stringify(cached))
|
|
643
|
-
return prev;
|
|
644
|
-
return cached;
|
|
645
|
-
});
|
|
646
|
-
return;
|
|
647
|
-
}
|
|
648
|
-
const res = await goralysFetchClient("GET", buildApiUrl("support/tickets", { "csrf-token": await fetchCsrfClient("get-tickets") }));
|
|
649
|
-
if (res)
|
|
650
|
-
await handleToastRequest(res, showToastRef.current, false);
|
|
651
|
-
const data = await res?.json();
|
|
652
|
-
cookiesSet(syncKey, "1");
|
|
653
|
-
storageSet(cacheKey, JSON.stringify(data));
|
|
654
|
-
const result = Array.isArray(data) ? data : null;
|
|
655
|
-
setSupportTickets((prev) => {
|
|
656
|
-
if (JSON.stringify(prev) === JSON.stringify(result))
|
|
657
|
-
return prev;
|
|
658
|
-
return result;
|
|
659
|
-
});
|
|
660
|
-
} finally {
|
|
661
|
-
inFlightRef.current = null;
|
|
662
|
-
resolve();
|
|
663
|
-
}
|
|
664
|
-
}, [showToastRef]);
|
|
665
|
-
useEffect2(() => {
|
|
666
|
-
const onChange = () => {
|
|
667
|
-
if (inFlightRef.current)
|
|
668
|
-
return;
|
|
669
|
-
if (cookiesGet(SUPPORT_TICKET_SYNC) != "1") {
|
|
670
|
-
void fetchSupportTickets();
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
return cookiesOnChange(onChange);
|
|
674
|
-
}, [fetchSupportTickets]);
|
|
675
|
-
useEffect2(() => {
|
|
676
|
-
void fetchSupportTickets();
|
|
677
|
-
}, [fetchSupportTickets]);
|
|
678
|
-
return useMemo2(() => ({
|
|
679
|
-
supportTickets,
|
|
680
|
-
refetch: fetchSupportTickets,
|
|
681
|
-
syncKey: SUPPORT_TICKET_SYNC
|
|
682
|
-
}), [supportTickets, fetchSupportTickets]);
|
|
616
|
+
// src/hooks/use-auth-tokens.ts
|
|
617
|
+
function useAuthTokens(context, target = null) {
|
|
618
|
+
const guard = useCallback2(() => context === "admin-panel" && !target ? "Cannot use admin panel context without target" : null, [context, target]);
|
|
619
|
+
const fetcher = useCallback2(async () => context === "admin-panel" ? await fetchTokensAdminPanel(target) : await fetchTokensProfile(), [context, target]);
|
|
620
|
+
const parse = useCallback2((data) => Array.isArray(data) ? data : null, []);
|
|
621
|
+
const { data: tokens, refetch, syncKey } = useSyncedResource({
|
|
622
|
+
name: "useAuthTokens",
|
|
623
|
+
cacheKey: AUTH_TOKEN_CACHES[context],
|
|
624
|
+
syncKey: AUTH_TOKEN_SYNCS[context],
|
|
625
|
+
fetcher,
|
|
626
|
+
parse,
|
|
627
|
+
guard
|
|
628
|
+
});
|
|
629
|
+
return { tokens, refetch, syncKey };
|
|
683
630
|
}
|
|
684
631
|
|
|
685
|
-
// src/hooks/
|
|
686
|
-
import { useCallback as useCallback3
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
632
|
+
// src/hooks/use-subjects.ts
|
|
633
|
+
import { useCallback as useCallback3 } from "react";
|
|
634
|
+
|
|
635
|
+
// src/lib/subjects/subjects.client.ts
|
|
636
|
+
async function fetchStudentSubjectsClient() {
|
|
637
|
+
const csrfToken = await fetchCsrfClient("get-student-subjects");
|
|
638
|
+
if (!csrfToken)
|
|
639
|
+
return null;
|
|
640
|
+
return await goralysFetchClient("GET", buildApiUrl("subjects/student", { "csrf-token": csrfToken }));
|
|
641
|
+
}
|
|
642
|
+
async function fetchTeacherSubjectsClient() {
|
|
643
|
+
const csrfToken = await fetchCsrfClient("get-teacher-subjects");
|
|
644
|
+
if (!csrfToken)
|
|
645
|
+
return null;
|
|
646
|
+
return await goralysFetchClient("GET", buildApiUrl("subjects/teacher", { "csrf-token": csrfToken }));
|
|
647
|
+
}
|
|
648
|
+
async function fetchAdminSubjectsClient() {
|
|
649
|
+
const csrfToken = await fetchCsrfClient("get-admin-subjects");
|
|
650
|
+
if (!csrfToken)
|
|
651
|
+
return null;
|
|
652
|
+
return await goralysFetchClient("GET", buildApiUrl("subjects/admin", { "csrf-token": csrfToken }));
|
|
653
|
+
}
|
|
654
|
+
async function fetchSubjectsForRoleClient(role) {
|
|
655
|
+
switch (role.role) {
|
|
656
|
+
case "student":
|
|
657
|
+
return await fetchStudentSubjectsClient();
|
|
658
|
+
case "teacher":
|
|
659
|
+
return await fetchTeacherSubjectsClient();
|
|
660
|
+
case "admin":
|
|
661
|
+
return await fetchAdminSubjectsClient();
|
|
662
|
+
default:
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/hooks/use-subjects.ts
|
|
668
|
+
function useSubjects(role) {
|
|
669
|
+
const fetcher = useCallback3(async () => fetchSubjectsForRoleClient({ role }), [role]);
|
|
670
|
+
const parse = useCallback3((data) => Array.isArray(data) ? data : null, []);
|
|
671
|
+
const { data: subjects, refetch, syncKey } = useSyncedResource({
|
|
672
|
+
name: "useSubjects",
|
|
673
|
+
cacheKey: SUBJECT_CACHES[role],
|
|
674
|
+
syncKey: SUBJECT_SYNCS[role],
|
|
675
|
+
fetcher,
|
|
676
|
+
parse
|
|
677
|
+
});
|
|
678
|
+
return { subjects, refetch, syncKey };
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// src/hooks/use-support-tickets.ts
|
|
682
|
+
import { useCallback as useCallback4 } from "react";
|
|
683
|
+
function useSupportTickets() {
|
|
684
|
+
const fetcher = useCallback4(async () => await goralysFetchClient("GET", buildApiUrl("support/tickets", { "csrf-token": await fetchCsrfClient("get-tickets") })), []);
|
|
685
|
+
const parse = useCallback4((data) => Array.isArray(data) ? data : null, []);
|
|
686
|
+
const { data: supportTickets, refetch, syncKey } = useSyncedResource({
|
|
687
|
+
name: "useSupportTickets",
|
|
688
|
+
cacheKey: SUPPORT_TICKET_CACHE,
|
|
689
|
+
syncKey: SUPPORT_TICKET_SYNC,
|
|
690
|
+
fetcher,
|
|
691
|
+
parse
|
|
692
|
+
});
|
|
693
|
+
return { supportTickets, refetch, syncKey };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/hooks/use-users-collection.ts
|
|
697
|
+
import { useCallback as useCallback5 } from "react";
|
|
698
|
+
function useUsersCollection(fetcher, type) {
|
|
699
|
+
const parse = useCallback5((data) => Array.isArray(data) ? data : null, []);
|
|
700
|
+
const { data: users, refetch, syncKey } = useSyncedResource({
|
|
701
|
+
name: "useUsersCollection",
|
|
702
|
+
cacheKey: USER_CACHES[type],
|
|
703
|
+
syncKey: USER_SYNCS[type],
|
|
704
|
+
fetcher,
|
|
705
|
+
parse
|
|
706
|
+
});
|
|
707
|
+
return { users, refetch, syncKey };
|
|
758
708
|
}
|
|
759
|
-
function useUsers(
|
|
760
|
-
return
|
|
709
|
+
function useUsers() {
|
|
710
|
+
return useUsersCollection(fetchUsersClient, "users-real");
|
|
761
711
|
}
|
|
762
|
-
function useVirtualUsers(
|
|
763
|
-
return
|
|
712
|
+
function useVirtualUsers() {
|
|
713
|
+
return useUsersCollection(fetchVirtualUsersClient, "users-virtual");
|
|
764
714
|
}
|
|
765
|
-
function useAdmins(
|
|
766
|
-
return
|
|
715
|
+
function useAdmins() {
|
|
716
|
+
return useUsersCollection(fetchAdminsClient, "admins-real");
|
|
767
717
|
}
|
|
768
|
-
function useVirtualAdmins(
|
|
769
|
-
return
|
|
718
|
+
function useVirtualAdmins() {
|
|
719
|
+
return useUsersCollection(fetchVirtualAdminsClient, "admins-virtual");
|
|
770
720
|
}
|
|
771
721
|
|
|
772
722
|
// src/lib/asset.ts
|
|
@@ -789,8 +739,9 @@ function isFoolsDay() {
|
|
|
789
739
|
function buildArray(...items) {
|
|
790
740
|
return items.filter((item) => Boolean(item));
|
|
791
741
|
}
|
|
742
|
+
var fromPhpDate = (phpDate) => new Date(phpDate.date);
|
|
792
743
|
var parsePhpDateTime = (phpDate) => {
|
|
793
|
-
return
|
|
744
|
+
return fromPhpDate(phpDate).toLocaleString("fr-FR", {
|
|
794
745
|
year: "numeric",
|
|
795
746
|
month: "2-digit",
|
|
796
747
|
day: "2-digit",
|
|
@@ -953,6 +904,10 @@ var USER_ROLES = ["admin", "teacher", "student", "none"];
|
|
|
953
904
|
export {
|
|
954
905
|
ADMIN_CACHE_RADIX,
|
|
955
906
|
ADMIN_SYNC_RADIX,
|
|
907
|
+
AUTH_TOKEN_CACHES,
|
|
908
|
+
AUTH_TOKEN_CACHE_RADIX,
|
|
909
|
+
AUTH_TOKEN_SYNCS,
|
|
910
|
+
AUTH_TOKEN_SYNC_RADIX,
|
|
956
911
|
COOKIES_DISMISSED_KEY,
|
|
957
912
|
EMAIL_KEY,
|
|
958
913
|
FLASH_TOAST_KEY,
|
|
@@ -984,6 +939,7 @@ export {
|
|
|
984
939
|
configureCookies,
|
|
985
940
|
configureGoralysClient,
|
|
986
941
|
configureStorage,
|
|
942
|
+
configureToast,
|
|
987
943
|
cookiesGet,
|
|
988
944
|
cookiesGetAll,
|
|
989
945
|
cookiesOnChange,
|
|
@@ -994,16 +950,21 @@ export {
|
|
|
994
950
|
emitUserEvent,
|
|
995
951
|
emptyUserCacheClient,
|
|
996
952
|
fetchAdminsClient,
|
|
953
|
+
fetchAuthTokensForContext,
|
|
997
954
|
fetchCsrfClient,
|
|
998
955
|
fetchSubjectsForRoleClient,
|
|
956
|
+
fetchTokensAdminPanel,
|
|
957
|
+
fetchTokensProfile,
|
|
999
958
|
fetchUsersClient,
|
|
1000
959
|
fetchVirtualAdminsClient,
|
|
1001
960
|
fetchVirtualUsersClient,
|
|
961
|
+
fromPhpDate,
|
|
1002
962
|
getGoralysClientConfig,
|
|
1003
963
|
getLongFromShort,
|
|
1004
964
|
getShortFromLong,
|
|
1005
965
|
getStatusHelper,
|
|
1006
966
|
getStatusLabel,
|
|
967
|
+
getToastConfig,
|
|
1007
968
|
goralysFetchClient,
|
|
1008
969
|
handleToastRequest,
|
|
1009
970
|
helpContent,
|
|
@@ -1021,8 +982,10 @@ export {
|
|
|
1021
982
|
storageSet,
|
|
1022
983
|
storageSize,
|
|
1023
984
|
useAdmins,
|
|
985
|
+
useAuthTokens,
|
|
1024
986
|
useSubjects,
|
|
1025
987
|
useSupportTickets,
|
|
988
|
+
useSyncedResource,
|
|
1026
989
|
useUsers,
|
|
1027
990
|
useVirtualAdmins,
|
|
1028
991
|
useVirtualUsers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goralys/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"license": "LGPL-2.1-or-later",
|
|
5
5
|
"description": "Shared business logic for the Goralys web application",
|
|
6
6
|
"repository": {
|
|
@@ -27,9 +27,11 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
+
"prebuild": "pnpm barrel",
|
|
30
31
|
"build": "tsup",
|
|
31
32
|
"typeCheck": "tsgo --noEmit",
|
|
32
|
-
"dev": "tsup --watch"
|
|
33
|
+
"dev": "tsup --watch",
|
|
34
|
+
"barrel": "rm ./src/index.ts && barrelsby -d ./src -l top"
|
|
33
35
|
},
|
|
34
36
|
"peerDependencies": {
|
|
35
37
|
"react": ">=18"
|