@hgdc/ui 0.74.1 → 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 +17 -8
- package/dist/product-switch.js +75 -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. */
|
|
@@ -75,13 +77,19 @@ export declare function startBackgroundRefresh(doc?: Document): HTMLIFrameElemen
|
|
|
75
77
|
export declare function productEntryBase(href: string): string | null;
|
|
76
78
|
/**
|
|
77
79
|
* Ouvre en arrière-plan la session des autres modules de la barre (ADR 0026) : un cadre caché à la
|
|
78
|
-
* fois vers `<base>/auth/hgdc?frame=open`, au plus une fois par jour et par module
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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.
|
|
83
91
|
*/
|
|
84
|
-
export declare function startSessionOpening(bases: ReadonlyArray<string>, doc?: Document): () => void;
|
|
92
|
+
export declare function startSessionOpening(bases: ReadonlyArray<string>, doc?: Document, current?: string): () => void;
|
|
85
93
|
/**
|
|
86
94
|
* Relecture en arrière-plan si l'entrée l'a demandé. Montée à la racine du document
|
|
87
95
|
* (`FamilyLoaderRoot`), pour toutes les pages, y compris celles hors de la coque : la page « il te
|
|
@@ -99,6 +107,7 @@ export declare function useProductSwitch(enabled?: boolean): void;
|
|
|
99
107
|
/**
|
|
100
108
|
* Dans la barre de Basalte (`ShellNavigation`) : ouvre la session des modules affichés servis sous
|
|
101
109
|
* la même origine, `SESSION_OPEN_DELAY_MS` après l'affichage (ADR 0026). `bases` : clé stable
|
|
102
|
-
* (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.
|
|
103
112
|
*/
|
|
104
|
-
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. */
|
|
@@ -210,33 +212,84 @@ export function productEntryBase(href) {
|
|
|
210
212
|
}
|
|
211
213
|
/**
|
|
212
214
|
* Ouvre en arrière-plan la session des autres modules de la barre (ADR 0026) : un cadre caché à la
|
|
213
|
-
* fois vers `<base>/auth/hgdc?frame=open`, au plus une fois par jour et par module
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
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.
|
|
218
226
|
*/
|
|
219
|
-
export function startSessionOpening(bases, doc = document) {
|
|
227
|
+
export function startSessionOpening(bases, doc = document, current) {
|
|
220
228
|
const win = doc.defaultView;
|
|
221
229
|
const saveData = win?.navigator?.connection
|
|
222
230
|
?.saveData;
|
|
223
|
-
if (saveData || doc.prerendering || openingPaused(win))
|
|
231
|
+
if (!win || saveData || doc.prerendering || openingPaused(win))
|
|
224
232
|
return () => { };
|
|
225
|
-
// Sans stockage, pas de limite par jour : rien ne part (sinon une connexion par module à chaque page).
|
|
226
|
-
const queue = bases.filter((base) => refreshDue(base, win, Date.now(), SESSION_OPEN_KEY_PREFIX, SESSION_OPEN_INTERVAL_MS, false));
|
|
227
233
|
let stopped = false;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (
|
|
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()))
|
|
232
238
|
return;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
+
})();
|
|
236
261
|
return () => {
|
|
237
262
|
stopped = true;
|
|
238
263
|
};
|
|
239
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
|
+
}
|
|
240
293
|
/**
|
|
241
294
|
* Relecture en arrière-plan si l'entrée l'a demandé. Montée à la racine du document
|
|
242
295
|
* (`FamilyLoaderRoot`), pour toutes les pages, y compris celles hors de la coque : la page « il te
|
|
@@ -275,20 +328,21 @@ export function useProductSwitch(enabled = true) {
|
|
|
275
328
|
/**
|
|
276
329
|
* Dans la barre de Basalte (`ShellNavigation`) : ouvre la session des modules affichés servis sous
|
|
277
330
|
* la même origine, `SESSION_OPEN_DELAY_MS` après l'affichage (ADR 0026). `bases` : clé stable
|
|
278
|
-
* (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.
|
|
279
333
|
*/
|
|
280
|
-
export function useSessionOpening(bases) {
|
|
334
|
+
export function useSessionOpening(bases, current) {
|
|
281
335
|
const key = bases.join(" ");
|
|
282
336
|
useEffect(() => {
|
|
283
|
-
if (!key)
|
|
337
|
+
if (!key || current === null)
|
|
284
338
|
return;
|
|
285
339
|
let stop = () => { };
|
|
286
340
|
const timer = window.setTimeout(() => {
|
|
287
|
-
stop = startSessionOpening(key.split(" "));
|
|
341
|
+
stop = startSessionOpening(key.split(" "), document, current);
|
|
288
342
|
}, SESSION_OPEN_DELAY_MS);
|
|
289
343
|
return () => {
|
|
290
344
|
window.clearTimeout(timer);
|
|
291
345
|
stop();
|
|
292
346
|
};
|
|
293
|
-
}, [key]);
|
|
347
|
+
}, [key, current]);
|
|
294
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",
|