@hgdc/ui 0.74.0 → 0.74.2
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/module-bar.d.ts +5 -0
- package/dist/module-bar.js +10 -2
- package/dist/product-switch.d.ts +22 -9
- package/dist/product-switch.js +84 -21
- package/package.json +1 -1
package/dist/module-bar.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export type ShellNavLevel = "tabs" | "modules" | "edit";
|
|
|
9
9
|
* ceux dont la session s'ouvre en arrière-plan (ADR 0026). Manager n'en fait pas partie.
|
|
10
10
|
*/
|
|
11
11
|
export declare function sessionBases(entries: ReadonlyArray<ShellProductEntry>, shown: ReadonlyArray<ModuleWord>, current: FamilyProduct): Array<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Base du module ouvert (`/mail`) si sa page est servie sous sa base, sinon `null` (rendu serveur,
|
|
14
|
+
* autre origine) : sa session décide de l'ouverture des autres (ADR 0026).
|
|
15
|
+
*/
|
|
16
|
+
export declare function currentSessionBase(entries: ReadonlyArray<ShellProductEntry>): string | null;
|
|
12
17
|
/**
|
|
13
18
|
* Le milieu de la barre de Basalte : Manager, un trait, puis l'un des trois niveaux. Dans un module,
|
|
14
19
|
* ses onglets (`nav`) sous « Retour aux modules » ; un clic y affiche la liste sans rien recharger ;
|
package/dist/module-bar.js
CHANGED
|
@@ -59,6 +59,14 @@ export function sessionBases(entries, shown, current) {
|
|
|
59
59
|
return base ? [base] : [];
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Base du module ouvert (`/mail`) si sa page est servie sous sa base, sinon `null` (rendu serveur,
|
|
64
|
+
* autre origine) : sa session décide de l'ouverture des autres (ADR 0026).
|
|
65
|
+
*/
|
|
66
|
+
export function currentSessionBase(entries) {
|
|
67
|
+
const href = entries.find((entry) => entry.current)?.href.replace(/\/$/, "");
|
|
68
|
+
return href && href.startsWith("/") ? href : null;
|
|
69
|
+
}
|
|
62
70
|
function ModuleList({ entries, order, onOpenCurrent, onCustomize, }) {
|
|
63
71
|
const shown = order.flatMap((word) => {
|
|
64
72
|
const entry = entries.find((item) => item.badge === word);
|
|
@@ -106,8 +114,8 @@ export function ShellNavigation({ product, nav, accountHref, level, onLevelChang
|
|
|
106
114
|
const { products, account } = shellProductEntries({ current: product, accountHref, pageOrigin, organization });
|
|
107
115
|
const shown = shownModules(order, product).filter((word) => !projectModules || word === product || projectModules.includes(word));
|
|
108
116
|
// Sessions des autres modules affichés, ouvertes en arrière-plan : le premier clic n'attend pas
|
|
109
|
-
// la connexion chez ID (ADR 0026).
|
|
110
|
-
useSessionOpening(sessionBases(products, shown, product));
|
|
117
|
+
// la connexion chez ID (ADR 0026). Seulement si le module ouvert a sa session : pas dans la démo sans compte.
|
|
118
|
+
useSessionOpening(sessionBases(products, shown, product), currentSessionBase(products));
|
|
111
119
|
return (_jsxs("div", { "data-slot": "shell-navigation", className: "flex min-h-0 flex-1 flex-col", children: [_jsxs("div", { "data-slot": "shell-manager", className: "shrink-0 px-3 pt-1", children: [_jsx("ul", { children: _jsx(ShellEntryLink, { entry: account, icon: UserGroupIcon }) }), _jsx("div", { "aria-hidden": true, className: "mx-2 mt-2 h-px bg-foreground/8" })] }), level === "modules" ? (_jsx(ModuleList, { entries: products, order: shown, onOpenCurrent: () => onLevelChange("tabs"), onCustomize: () => onLevelChange("edit") })) : level === "edit" ? (_jsx(ModuleBarEditor, { order: order, onSave: (next) => {
|
|
112
120
|
saveModuleBar(next);
|
|
113
121
|
void pushModuleBar(next);
|
package/dist/product-switch.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare const BACKGROUND_REFRESH_INTERVAL_MS: number;
|
|
|
15
15
|
export declare const ORG_SYNC_LAST_KEY_PREFIX = "hgdc-org-sync:";
|
|
16
16
|
/** Cadre caché qui ouvre la session d'un autre produit, seulement si elle manque (ADR 0026). */
|
|
17
17
|
export declare const SESSION_OPEN_PATH = "/auth/hgdc?frame=open";
|
|
18
|
+
/** Session du module ouvert, lue avant toute ouverture : la route better-auth sous sa base. */
|
|
19
|
+
export declare const SESSION_CHECK_PATH = "/api/auth/get-session";
|
|
18
20
|
/** Une ouverture au plus par produit pendant ce délai : une session vit 7 jours (`@hgdc/auth`). */
|
|
19
21
|
export declare const SESSION_OPEN_INTERVAL_MS: number;
|
|
20
22
|
/** Attente après l'affichage de la page avant la première ouverture : la page passe d'abord. */
|
|
@@ -27,7 +29,11 @@ export declare const SESSION_OPEN_KEY_PREFIX = "hgdc-session-open:";
|
|
|
27
29
|
* déconnexion (changer de compte, poste partagé) n'est jamais défaite sans la personne.
|
|
28
30
|
*/
|
|
29
31
|
export declare const SESSION_OPEN_PAUSED_KEY = "hgdc-session-open:paused";
|
|
30
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* À la déconnexion d'un module : plus d'ouverture en arrière-plan jusqu'à la prochaine connexion, et
|
|
34
|
+
* les cadres d'ouverture en cours sont retirés (un cadre en vol reposerait la session d'un module
|
|
35
|
+
* juste après la déconnexion).
|
|
36
|
+
*/
|
|
31
37
|
export declare function pauseSessionOpening(win?: Window | null): void;
|
|
32
38
|
/** Marque d'un lien de la barre vers un autre produit servi sous la même origine : prérendu au survol. */
|
|
33
39
|
export declare const PRODUCT_PRERENDER_ATTRIBUTE = "data-hgdc-prerender";
|
|
@@ -71,13 +77,19 @@ export declare function startBackgroundRefresh(doc?: Document): HTMLIFrameElemen
|
|
|
71
77
|
export declare function productEntryBase(href: string): string | null;
|
|
72
78
|
/**
|
|
73
79
|
* Ouvre en arrière-plan la session des autres modules de la barre (ADR 0026) : un cadre caché à la
|
|
74
|
-
* fois vers `<base>/auth/hgdc?frame=open`, au plus une fois par jour et par module
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
80
|
+
* fois vers `<base>/auth/hgdc?frame=open`, au plus une fois par jour et par module, et seulement pour
|
|
81
|
+
* un module sans session : sa session est lue d'abord (`SESSION_CHECK_PATH`, même origine). Déjà
|
|
82
|
+
* ouverte : pas de cadre, la limite du jour est marquée. Lecture en échec : pas de cadre, la limite
|
|
83
|
+
* n'est pas consommée. Sinon, la connexion HGDC se fait dans le cadre ; si ID demande quelque chose
|
|
84
|
+
* (session expirée, consentement), il refuse d'être affiché dans un cadre et rien ne se passe : le
|
|
85
|
+
* clic fera la connexion habituelle. Pas sur une page prérendue, ni quand l'économie de données est
|
|
86
|
+
* demandée. Rend une fonction qui arrête la file.
|
|
87
|
+
*
|
|
88
|
+
* `current` : base du module ouvert. Donnée, rien ne part tant que ce module n'a pas de session : un
|
|
89
|
+
* visiteur de la démo, sans compte, n'a pas de session d'ID à reprendre, et chaque cadre partirait
|
|
90
|
+
* pour rien vers ID.
|
|
79
91
|
*/
|
|
80
|
-
export declare function startSessionOpening(bases: ReadonlyArray<string>, doc?: Document): () => void;
|
|
92
|
+
export declare function startSessionOpening(bases: ReadonlyArray<string>, doc?: Document, current?: string): () => void;
|
|
81
93
|
/**
|
|
82
94
|
* Relecture en arrière-plan si l'entrée l'a demandé. Montée à la racine du document
|
|
83
95
|
* (`FamilyLoaderRoot`), pour toutes les pages, y compris celles hors de la coque : la page « il te
|
|
@@ -95,6 +107,7 @@ export declare function useProductSwitch(enabled?: boolean): void;
|
|
|
95
107
|
/**
|
|
96
108
|
* Dans la barre de Basalte (`ShellNavigation`) : ouvre la session des modules affichés servis sous
|
|
97
109
|
* la même origine, `SESSION_OPEN_DELAY_MS` après l'affichage (ADR 0026). `bases` : clé stable
|
|
98
|
-
* (les bases jointes), pour ne pas relancer la file à chaque rendu.
|
|
110
|
+
* (les bases jointes), pour ne pas relancer la file à chaque rendu. `current` : base du module ouvert
|
|
111
|
+
* (`startSessionOpening`) ; `null` (base inconnue) : rien ne part.
|
|
99
112
|
*/
|
|
100
|
-
export declare function useSessionOpening(bases: ReadonlyArray<string
|
|
113
|
+
export declare function useSessionOpening(bases: ReadonlyArray<string>, current?: string | null): void;
|
package/dist/product-switch.js
CHANGED
|
@@ -26,6 +26,8 @@ export const BACKGROUND_REFRESH_INTERVAL_MS = 10 * 60_000;
|
|
|
26
26
|
export const ORG_SYNC_LAST_KEY_PREFIX = "hgdc-org-sync:";
|
|
27
27
|
/** Cadre caché qui ouvre la session d'un autre produit, seulement si elle manque (ADR 0026). */
|
|
28
28
|
export const SESSION_OPEN_PATH = "/auth/hgdc?frame=open";
|
|
29
|
+
/** Session du module ouvert, lue avant toute ouverture : la route better-auth sous sa base. */
|
|
30
|
+
export const SESSION_CHECK_PATH = "/api/auth/get-session";
|
|
29
31
|
/** Une ouverture au plus par produit pendant ce délai : une session vit 7 jours (`@hgdc/auth`). */
|
|
30
32
|
export const SESSION_OPEN_INTERVAL_MS = 24 * 60 * 60_000;
|
|
31
33
|
/** Attente après l'affichage de la page avant la première ouverture : la page passe d'abord. */
|
|
@@ -38,7 +40,13 @@ export const SESSION_OPEN_KEY_PREFIX = "hgdc-session-open:";
|
|
|
38
40
|
* déconnexion (changer de compte, poste partagé) n'est jamais défaite sans la personne.
|
|
39
41
|
*/
|
|
40
42
|
export const SESSION_OPEN_PAUSED_KEY = "hgdc-session-open:paused";
|
|
41
|
-
/**
|
|
43
|
+
/** Marque des cadres d'ouverture (`startSessionOpening`), retirés par `pauseSessionOpening`. */
|
|
44
|
+
const SESSION_OPEN_FRAME_ATTRIBUTE = "data-hgdc-session-open";
|
|
45
|
+
/**
|
|
46
|
+
* À la déconnexion d'un module : plus d'ouverture en arrière-plan jusqu'à la prochaine connexion, et
|
|
47
|
+
* les cadres d'ouverture en cours sont retirés (un cadre en vol reposerait la session d'un module
|
|
48
|
+
* juste après la déconnexion).
|
|
49
|
+
*/
|
|
42
50
|
export function pauseSessionOpening(win = typeof window === "undefined" ? null : window) {
|
|
43
51
|
try {
|
|
44
52
|
win?.localStorage.setItem(SESSION_OPEN_PAUSED_KEY, String(Date.now()));
|
|
@@ -46,6 +54,8 @@ export function pauseSessionOpening(win = typeof window === "undefined" ? null :
|
|
|
46
54
|
catch {
|
|
47
55
|
// Stockage refusé : l'ouverture n'a pas lieu non plus sans lui (`startSessionOpening`).
|
|
48
56
|
}
|
|
57
|
+
for (const frame of win?.document.querySelectorAll(`iframe[${SESSION_OPEN_FRAME_ATTRIBUTE}]`) ?? [])
|
|
58
|
+
frame.remove();
|
|
49
59
|
}
|
|
50
60
|
/** Marque d'un lien de la barre vers un autre produit servi sous la même origine : prérendu au survol. */
|
|
51
61
|
export const PRODUCT_PRERENDER_ATTRIBUTE = "data-hgdc-prerender";
|
|
@@ -202,32 +212,84 @@ export function productEntryBase(href) {
|
|
|
202
212
|
}
|
|
203
213
|
/**
|
|
204
214
|
* Ouvre en arrière-plan la session des autres modules de la barre (ADR 0026) : un cadre caché à la
|
|
205
|
-
* fois vers `<base>/auth/hgdc?frame=open`, au plus une fois par jour et par module
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
215
|
+
* fois vers `<base>/auth/hgdc?frame=open`, au plus une fois par jour et par module, et seulement pour
|
|
216
|
+
* un module sans session : sa session est lue d'abord (`SESSION_CHECK_PATH`, même origine). Déjà
|
|
217
|
+
* ouverte : pas de cadre, la limite du jour est marquée. Lecture en échec : pas de cadre, la limite
|
|
218
|
+
* n'est pas consommée. Sinon, la connexion HGDC se fait dans le cadre ; si ID demande quelque chose
|
|
219
|
+
* (session expirée, consentement), il refuse d'être affiché dans un cadre et rien ne se passe : le
|
|
220
|
+
* clic fera la connexion habituelle. Pas sur une page prérendue, ni quand l'économie de données est
|
|
221
|
+
* demandée. Rend une fonction qui arrête la file.
|
|
222
|
+
*
|
|
223
|
+
* `current` : base du module ouvert. Donnée, rien ne part tant que ce module n'a pas de session : un
|
|
224
|
+
* visiteur de la démo, sans compte, n'a pas de session d'ID à reprendre, et chaque cadre partirait
|
|
225
|
+
* pour rien vers ID.
|
|
210
226
|
*/
|
|
211
|
-
export function startSessionOpening(bases, doc = document) {
|
|
227
|
+
export function startSessionOpening(bases, doc = document, current) {
|
|
212
228
|
const win = doc.defaultView;
|
|
213
229
|
const saveData = win?.navigator?.connection
|
|
214
230
|
?.saveData;
|
|
215
|
-
if (saveData || doc.prerendering || openingPaused(win))
|
|
231
|
+
if (!win || saveData || doc.prerendering || openingPaused(win))
|
|
216
232
|
return () => { };
|
|
217
|
-
// Sans stockage, pas de limite par jour : rien ne part (sinon une connexion par module à chaque page).
|
|
218
|
-
const queue = bases.filter((base) => refreshDue(base, win, Date.now(), SESSION_OPEN_KEY_PREFIX, SESSION_OPEN_INTERVAL_MS, false));
|
|
219
233
|
let stopped = false;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
234
|
+
// Relu avant chaque étape et après chaque lecture : une déconnexion pendant la file l'arrête.
|
|
235
|
+
const halted = () => stopped || openingPaused(win);
|
|
236
|
+
void (async () => {
|
|
237
|
+
if (current !== undefined && ((await readSession(win, current)) !== true || halted()))
|
|
223
238
|
return;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
239
|
+
for (const base of bases) {
|
|
240
|
+
if (halted())
|
|
241
|
+
return;
|
|
242
|
+
// Sans stockage, pas de limite par jour : rien ne part (sinon une connexion par module à chaque page).
|
|
243
|
+
if (!sessionOpenDue(win, base))
|
|
244
|
+
continue;
|
|
245
|
+
const open = await readSession(win, base);
|
|
246
|
+
if (halted())
|
|
247
|
+
return;
|
|
248
|
+
// Lecture en échec : rien à décider, la limite reste libre pour la prochaine page.
|
|
249
|
+
if (open === null)
|
|
250
|
+
continue;
|
|
251
|
+
// Marque la limite ; faux si un autre onglet vient de le faire, ou si le stockage refuse.
|
|
252
|
+
if (!refreshDue(base, win, Date.now(), SESSION_OPEN_KEY_PREFIX, SESSION_OPEN_INTERVAL_MS, false))
|
|
253
|
+
continue;
|
|
254
|
+
if (open)
|
|
255
|
+
continue;
|
|
256
|
+
await new Promise((resolve) => {
|
|
257
|
+
openHiddenFrame(doc, `${base}${SESSION_OPEN_PATH}`, resolve).setAttribute(SESSION_OPEN_FRAME_ATTRIBUTE, "");
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
})();
|
|
227
261
|
return () => {
|
|
228
262
|
stopped = true;
|
|
229
263
|
};
|
|
230
264
|
}
|
|
265
|
+
/** Vrai si la limite du jour de ce module est libre, sans la marquer. Stockage refusé : faux. */
|
|
266
|
+
function sessionOpenDue(win, base) {
|
|
267
|
+
try {
|
|
268
|
+
const last = Number(win.localStorage.getItem(`${SESSION_OPEN_KEY_PREFIX}${base || "/"}`));
|
|
269
|
+
const age = Date.now() - last;
|
|
270
|
+
return !(Number.isFinite(last) && last > 0 && age >= 0 && age < SESSION_OPEN_INTERVAL_MS);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/** Session du module de `base` : ouverte, absente, ou `null` si la lecture échoue. */
|
|
277
|
+
async function readSession(win, base) {
|
|
278
|
+
try {
|
|
279
|
+
const response = await win.fetch(`${base}${SESSION_CHECK_PATH}`, { credentials: "include", cache: "no-store" });
|
|
280
|
+
if (!response.ok)
|
|
281
|
+
return null;
|
|
282
|
+
const body = await response.json();
|
|
283
|
+
if (body === null)
|
|
284
|
+
return false;
|
|
285
|
+
if (typeof body !== "object")
|
|
286
|
+
return null;
|
|
287
|
+
return "session" in body && Boolean(body.session);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
231
293
|
/**
|
|
232
294
|
* Relecture en arrière-plan si l'entrée l'a demandé. Montée à la racine du document
|
|
233
295
|
* (`FamilyLoaderRoot`), pour toutes les pages, y compris celles hors de la coque : la page « il te
|
|
@@ -266,20 +328,21 @@ export function useProductSwitch(enabled = true) {
|
|
|
266
328
|
/**
|
|
267
329
|
* Dans la barre de Basalte (`ShellNavigation`) : ouvre la session des modules affichés servis sous
|
|
268
330
|
* la même origine, `SESSION_OPEN_DELAY_MS` après l'affichage (ADR 0026). `bases` : clé stable
|
|
269
|
-
* (les bases jointes), pour ne pas relancer la file à chaque rendu.
|
|
331
|
+
* (les bases jointes), pour ne pas relancer la file à chaque rendu. `current` : base du module ouvert
|
|
332
|
+
* (`startSessionOpening`) ; `null` (base inconnue) : rien ne part.
|
|
270
333
|
*/
|
|
271
|
-
export function useSessionOpening(bases) {
|
|
334
|
+
export function useSessionOpening(bases, current) {
|
|
272
335
|
const key = bases.join(" ");
|
|
273
336
|
useEffect(() => {
|
|
274
|
-
if (!key)
|
|
337
|
+
if (!key || current === null)
|
|
275
338
|
return;
|
|
276
339
|
let stop = () => { };
|
|
277
340
|
const timer = window.setTimeout(() => {
|
|
278
|
-
stop = startSessionOpening(key.split(" "));
|
|
341
|
+
stop = startSessionOpening(key.split(" "), document, current);
|
|
279
342
|
}, SESSION_OPEN_DELAY_MS);
|
|
280
343
|
return () => {
|
|
281
344
|
window.clearTimeout(timer);
|
|
282
345
|
stop();
|
|
283
346
|
};
|
|
284
|
-
}, [key]);
|
|
347
|
+
}, [key, current]);
|
|
285
348
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hgdc/ui",
|
|
3
|
-
"version": "0.74.
|
|
3
|
+
"version": "0.74.2",
|
|
4
4
|
"description": "Interface de la H Collection : jetons de la DA, thèmes, polices et composants.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/ui#readme",
|