@liorian/sdk 0.33.0-alpha.e125804 → 0.38.0-alpha.7aa2e5b

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.
Files changed (72) hide show
  1. package/README.md +2 -2
  2. package/package.json +2 -2
  3. package/schemas/module.schema.json +1 -1
  4. package/src/application/service/auth-user.service.ts +114 -4
  5. package/src/application/service/config-module-api.service.ts +95 -0
  6. package/src/application/service/module-installation-api.service.ts +118 -0
  7. package/src/application/service/notifications-push.service.ts +14 -0
  8. package/src/domain/entities/catalog.interface.ts +3 -0
  9. package/src/domain/entities/index.ts +1 -0
  10. package/src/domain/entities/module-configuration.interface.ts +68 -0
  11. package/src/domain/entities/module-installation.interface.ts +165 -0
  12. package/src/domain/entities/module.interface.ts +4 -0
  13. package/src/domain/enums/user-profile.enum.ts +4 -4
  14. package/src/infrastructure/hooks/index.ts +2 -0
  15. package/src/infrastructure/hooks/module-installation.ts +168 -0
  16. package/src/infrastructure/hooks/use-sso-login.ts +19 -0
  17. package/src/infrastructure/module-installer/declare-module-from-manifest.ts +62 -0
  18. package/src/infrastructure/module-installer/index.ts +4 -0
  19. package/src/infrastructure/module-installer/module-installer.service.ts +138 -0
  20. package/src/infrastructure/providers/auth.provider.tsx +66 -4
  21. package/src/infrastructure/providers/modules-guard.provider.tsx +43 -22
  22. package/src/infrastructure/providers/query-client.ts +40 -0
  23. package/src/infrastructure/providers/query.provider.tsx +3 -12
  24. package/src/infrastructure/stores/auth-user-connected.store.ts +17 -1
  25. package/src/infrastructure/stores/index.ts +1 -1
  26. package/src/infrastructure/stores/module-installation.store.ts +265 -0
  27. package/src/infrastructure/stores/module.store.ts +45 -24
  28. package/src/infrastructure/utilities/activity-lock.service.ts +7 -1
  29. package/src/infrastructure/utilities/api-service.ts +59 -23
  30. package/src/infrastructure/utilities/auth-logout.util.ts +7 -6
  31. package/src/infrastructure/utilities/index.tsx +3 -0
  32. package/src/infrastructure/utilities/jwt.util.ts +75 -0
  33. package/src/infrastructure/utilities/local-data-purge.util.ts +324 -0
  34. package/src/infrastructure/utilities/session-events.ts +41 -0
  35. package/src/infrastructure/utilities/sso-auth.util.ts +127 -15
  36. package/src/presentation/components/auth/sso-callback.view.tsx +28 -4
  37. package/src/presentation/components/auth-lock-screen.tsx +52 -4
  38. package/src/presentation/components/index.tsx +2 -0
  39. package/src/presentation/components/module-configuration/index.tsx +3 -0
  40. package/src/presentation/components/module-configuration/module-configuration.modal.tsx +48 -0
  41. package/src/presentation/components/module-configuration/module-configuration.provider.tsx +178 -0
  42. package/src/presentation/components/module-configuration/module-configuration.steps.tsx +112 -0
  43. package/src/presentation/components/module-installation/index.ts +7 -0
  44. package/src/presentation/components/module-installation/module-compatibility-badge.tsx +65 -0
  45. package/src/presentation/components/module-installation/module-install-progress.tsx +95 -0
  46. package/src/presentation/components/module-installation/module-permission-review.tsx +134 -0
  47. package/src/presentation/components/module-installation/module-state-badge.tsx +55 -0
  48. package/src/presentation/components/module-installation/module-version-selector.tsx +67 -0
  49. package/src/presentation/components/module-runtime/declarative/blocks/declarative-chart-block.tsx +124 -0
  50. package/src/presentation/components/module-runtime/declarative/blocks/declarative-datagrid-block.tsx +220 -0
  51. package/src/presentation/components/module-runtime/declarative/blocks/declarative-form-block.tsx +132 -0
  52. package/src/presentation/components/module-runtime/declarative/blocks/declarative-form-fields.tsx +213 -0
  53. package/src/presentation/components/module-runtime/declarative/blocks/declarative-kpi-block.tsx +41 -0
  54. package/src/presentation/components/module-runtime/declarative/blocks/declarative-settings-block.tsx +200 -0
  55. package/src/presentation/components/module-runtime/declarative/blocks/declarative-widgets-block.tsx +58 -0
  56. package/src/presentation/components/module-runtime/declarative/declarative-data.ts +83 -0
  57. package/src/presentation/components/module-runtime/declarative/declarative-engine.tsx +95 -0
  58. package/src/presentation/components/module-runtime/declarative/declarative-types.ts +159 -0
  59. package/src/presentation/components/module-runtime/declarative-host.tsx +63 -0
  60. package/src/presentation/components/module-runtime/iframe-host.tsx +54 -0
  61. package/src/presentation/components/module-runtime/use-module-route.ts +76 -0
  62. package/src/presentation/index.tsx +1 -0
  63. package/src/presentation/modals/components/ModalWrapper.tsx +14 -8
  64. package/src/presentation/modals/types/modal.type.ts +2 -0
  65. package/src/presentation/settings/index.tsx +2 -0
  66. package/src/presentation/settings/settings.tsx +173 -0
  67. package/src/presentation/ui/alert-dialog.tsx +11 -1
  68. package/src/presentation/ui/combobox.tsx +8 -2
  69. package/src/presentation/ui/dialog.tsx +21 -14
  70. package/src/presentation/ui/drawer.tsx +9 -2
  71. package/src/presentation/ui/portal-container.tsx +48 -0
  72. package/src/presentation/ui/sheet.tsx +21 -14
package/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  Design system, shell applicatif et socle partagé du monorepo **Liorian**.
4
4
 
5
5
  Le package concentre l'ensemble des briques réutilisables utilisées par les hôtes frontend du
6
- monorepo (`frontend/liorian-socle`, `frontend/liorian-auth`, `frontend/liorian-connect`,
7
- `frontend/liorian-console`, `frontend/liorian-store`) ainsi que par les modules externes
6
+ monorepo (`apps/liorian-socle`, `apps/liorian-auth`, `apps/liorian-connect`,
7
+ `apps/liorian-console`, `apps/liorian-store`) ainsi que par les modules externes
8
8
  déclarés par les applications : primitives UI (style shadcn), thème **Katon**, composants
9
9
  composés (`DataGrid`, modales, sheets, lecteur multimédia…), providers, hooks, stores zustand,
10
10
  routines, services API, domaines, enums et utilitaires.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@liorian/sdk",
3
- "version": "0.33.0-alpha.e125804",
4
- "description": "Core package for the Liorian (application shell, domain, infrastructure, UI design system).",
3
+ "version": "0.38.0-alpha.7aa2e5b",
4
+ "description": "Core package for the Liora (application shell, domain, infrastructure, UI design system).",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "module-manifest.schema.json",
4
- "$comment": "Schéma canonique du manifest.json des modules Liorian (@liorian/sdk). Référencé par les modules du socle (frontend/liorian-socle/src/modules/*) et les modules externes (frontend/liorian-socle/library/modules/*).",
4
+ "$comment": "Schéma canonique du manifest.json des modules Liorian (@liorian/sdk). Référencé par les modules du socle (apps/liorian-socle/src/modules/*) et les modules externes (apps/liorian-socle/library/modules/*).",
5
5
  "title": "Module Manifest (Liorian)",
6
6
  "description": "Manifeste d'installation et de déclaration d'un module Liorian. Ce fichier décrit l'identité du module, sa compatibilité (plateformes, versions socle/API), ses permissions, ses scopes OAuth, ses capacités Tauri et ses prérequis fonctionnels (requirements/optionalRequirements). Les dépendances npm d'exécution et de développement sont portées par le `package.json` du module. Il est la source de vérité des métadonnées techniques d'un module, consommée par le Module Store et l'outillage de distribution.",
7
7
  "type": "object",
@@ -7,6 +7,10 @@ import {UserInterface} from "../../domain/entities/user.interface";
7
7
  import {UserAuthResponseInterface} from "../../domain/entities/user-auth.interface";
8
8
  import {OrganizationInterface} from "../../domain/entities/organization.interface";
9
9
  import {AuthConfig} from "../../domain/config/auth.config";
10
+ import {ActivityLockService} from "../../infrastructure/utilities/activity-lock.service";
11
+ import {emitSessionExpired} from "../../infrastructure/utilities/session-events";
12
+ import {getTokenExpiration, isTokenExpired} from "../../infrastructure/utilities/jwt.util";
13
+ import {LocalDataPurgeService} from "../../infrastructure/utilities/local-data-purge.util";
10
14
 
11
15
  const instance = new CacheStorageCapability<AuthCacheInterface>(AuthConfig.storages.sessionKey, CacheStorageType.LOCAL);
12
16
 
@@ -54,8 +58,35 @@ export class AuthUserService {
54
58
  return instance.get('apiKey') ?? null;
55
59
  }
56
60
 
61
+ /**
62
+ * Timestamp d'expiration du jeton courant, ou `null` si le jeton est absent
63
+ * ou ne porte pas d'`exp` lisible. Permet de savoir *quand* la session
64
+ * expirera au lieu de le découvrir par une série de 401.
65
+ */
66
+ static getTokenExpiration(): number | null {
67
+ return getTokenExpiration(this.getToken());
68
+ }
69
+
70
+ /**
71
+ * Vrai si un jeton est présent **et** non expiré.
72
+ *
73
+ * L'expiration est évaluée localement sur la claim `exp` du JWT : sans
74
+ * cela, un jeton expiré depuis des heures reste considéré comme
75
+ * « connecté » jusqu'au prochain appel à l'API — l'application traverse
76
+ * alors une fenêtre où elle s'affiche connectée mais ne peut plus rien
77
+ * faire, et l'écran de connexion peut se retrouver dans un état
78
+ * incohérent.
79
+ *
80
+ * Un jeton non-JSON (opaque) n'est jamais considéré expiré ici : seule
81
+ * l'API peut trancher.
82
+ */
83
+ static isTokenValid(): boolean {
84
+ const token = this.getToken();
85
+ return !!token && !isTokenExpired(token);
86
+ }
87
+
57
88
  static isAuthenticated(): boolean {
58
- return !!this.getToken();
89
+ return this.isTokenValid();
59
90
  }
60
91
 
61
92
  static async setUser(user: UserInterface) {
@@ -83,21 +114,100 @@ export class AuthUserService {
83
114
  await instance.set('apiKey', apiKey).save();
84
115
  }
85
116
 
117
+ /**
118
+ * Installe une session neuve (connexion directe ou échange du code OAuth).
119
+ *
120
+ * Effets de bord :
121
+ * - l'organisation courante et la clé d'API de la session précédente sont
122
+ * purgées : elles portent sur l'identité et le périmètre de l'ancienne
123
+ * session et seraient sinon renvoyées avec le **nouveau** jeton
124
+ * (`X-Organization-Id`, `X-API-KEY`), ce qui est à la fois inutile et
125
+ * faussé ;
126
+ * - le verrou d'inactivité, **persisté** dans `localStorage`, est levé.
127
+ * Sans cela, un verrou laissé actif survit à la reconnexion :
128
+ * `AuthProvider`/`AuthGuard` ré-affichent alors immédiatement l'écran de
129
+ * verrouillage et l'utilisateur reste bloqué sur l'écran de connexion
130
+ * malgré une session parfaitement valide.
131
+ */
86
132
  static async setSession(payload: UserAuthResponseInterface) {
133
+ // Purge préalable des traces du compte précédent : sans cela, les
134
+ // données locales (modules activés, panier, notifications lues,
135
+ // brouillons…) survivent et corrompent la session qui s'installe —
136
+ // éventuellement au profit d'un autre utilisateur sur le même
137
+ // navigateur. La purge est sélective (préférences d'appareil
138
+ // conservées) et best-effort : elle ne bloque jamais l'installation.
139
+ // Les secrets PKCE lus avant l'échange ont déjà été copiés en mémoire
140
+ // par l'appelant, leur suppression ici est donc sans danger.
141
+ try {
142
+ await LocalDataPurgeService.purge('new-session');
143
+ } catch {
144
+ // Best-effort : l'installation de la session prime.
145
+ }
146
+
87
147
  const {user, token, device, organizations} = payload;
88
148
  instance
89
149
  .set('user', user)
90
150
  .set('token', token)
91
151
  .set('device', device)
92
- .set('organizations', organizations);
152
+ .set('organizations', organizations)
153
+ .set('currentOrganization', null)
154
+ .set('apiKey', null);
93
155
 
94
156
  await instance.save();
95
157
  setCookie('token', token);
96
- }
97
158
 
98
- static async clear() {
159
+ // S'authentifier, c'est déverrouiller : la nouvelle session prend le
160
+ // pas sur tout verrou d'inactivité résiduel.
161
+ ActivityLockService.unlock();
162
+ }
163
+
164
+ /**
165
+ * Purge **totale** des données locales rattachées au compte.
166
+ *
167
+ * Unique point de sortie de la session locale, emprunté par la déconnexion
168
+ * comme par l'expiration. Au-delà de la session chiffrée et du cookie
169
+ * `token`, elle délègue à {@link LocalDataPurgeService} l'effacement de
170
+ * toutes les autres traces du compte :
171
+ *
172
+ * - `localStorage` : verrou d'inactivité, modules activés, état
173
+ * d'installation, secrets PKCE OAuth, notifications lues, modules récents ;
174
+ * - `sessionStorage` : brouillon des formulaires en cours de saisie ;
175
+ * - cookies rattachés au compte ;
176
+ * - mémoire : cache React Query, magasins Zustand (via les hooks
177
+ * enregistrés par leurs auteurs).
178
+ *
179
+ * Sans cela, ces données survivent à la fin de session et réapparaissent à la
180
+ * connexion suivante — éventuellement au profit d'un autre utilisateur sur le
181
+ * même navigateur.
182
+ */
183
+ static async clear(reason: string = "unknown") {
99
184
  await instance.clear().save();
100
185
  deleteCookie('token');
186
+ ActivityLockService.unlock();
187
+ await LocalDataPurgeService.purge(reason);
188
+ }
189
+
190
+ /**
191
+ * Point d'entrée unique d'invalidation de session.
192
+ *
193
+ * Purge la session locale puis notifie les abonnés (`AuthProvider`) afin
194
+ * que l'interface passe en écran de connexion. L'opération est
195
+ * **idempotente** : une rafale de 401 simultanées n'émet qu'un seul
196
+ * événement, et une application déjà déconnectée n'émet rien.
197
+ *
198
+ * @returns `true` si une session a effectivement été invalidée.
199
+ */
200
+ static async invalidate(source: string = 'unknown'): Promise<boolean> {
201
+ // Attend le déchiffrement du stockage : sans cela, une 401 reçue avant
202
+ // la fin de l'hydratation verrait une session « absente » et
203
+ // n'émettrait aucune invalidation.
204
+ await this.ready();
205
+
206
+ if (!instance.get('token')) return false;
207
+
208
+ await this.clear(source);
209
+ emitSessionExpired({source});
210
+ return true;
101
211
  }
102
212
 
103
213
  static getCsrfToken(): string | null {
@@ -0,0 +1,95 @@
1
+ import {ApiService} from "../../infrastructure/utilities/api-service";
2
+ import {FetchResponseInterface} from "../../domain/typing/response";
3
+
4
+ /**
5
+ * Service API du CRUD auto-généré des modules `CONFIGURATION`
6
+ * (T-504/S-028, FR-026) — cible `liorian-api-core` `/config-modules`.
7
+ *
8
+ * L'organisation est portée par l'en-tête `X-Organization-Id` (middleware
9
+ * `OrganizationApiKeyMiddleware`) ; le jeton proxy scopé d'un module iframe
10
+ * est attaché par la couche HTTP du socle (T-506).
11
+ */
12
+
13
+ export interface ConfigModuleRecord {
14
+ id: string;
15
+ data: Record<string, any>;
16
+ createdAt: string | null;
17
+ updatedAt: string | null;
18
+ }
19
+
20
+ export interface ConfigModuleListResponse {
21
+ rows: ConfigModuleRecord[];
22
+ total: number;
23
+ }
24
+
25
+ export interface ConfigModuleAggregatePayload {
26
+ fn?: "count" | "sum" | "mean";
27
+ /** Champ agrégé — omitir pour `__count`. */
28
+ field?: string;
29
+ /** Clé de regroupement (charts) — produit `rows: [{key, value}]`. */
30
+ groupBy?: string;
31
+ }
32
+
33
+ export interface ConfigModuleAggregateScalar {
34
+ value: number;
35
+ field: string;
36
+ fn: string;
37
+ }
38
+
39
+ export interface ConfigModuleAggregateGrouped {
40
+ fn: string;
41
+ field: string;
42
+ groupBy: string;
43
+ rows: Array<{key: string; value: number}>;
44
+ }
45
+
46
+ export type ConfigModuleAggregateResponse = ConfigModuleAggregateScalar | ConfigModuleAggregateGrouped;
47
+
48
+ export class ConfigModuleApiService extends ApiService {
49
+
50
+ /** Lister les enregistrements d'une ressource (`?search=`). */
51
+ static async list(moduleIdentifier: string, resource: string, options?: {search?: string}) {
52
+ return await this.get<FetchResponseInterface<ConfigModuleListResponse>>(
53
+ `/config-modules/${moduleIdentifier}/${resource}`,
54
+ options?.search ? {search: options.search} : undefined,
55
+ );
56
+ }
57
+
58
+ /** Obtenir un enregistrement. */
59
+ static async findOne(moduleIdentifier: string, resource: string, recordId: string) {
60
+ return await this.get<FetchResponseInterface<ConfigModuleRecord>>(
61
+ `/config-modules/${moduleIdentifier}/${resource}/${recordId}`,
62
+ );
63
+ }
64
+
65
+ /** Créer un enregistrement. */
66
+ static async create(moduleIdentifier: string, resource: string, data: Record<string, any>) {
67
+ return await this.post<FetchResponseInterface<ConfigModuleRecord>>(
68
+ `/config-modules/${moduleIdentifier}/${resource}`,
69
+ data,
70
+ );
71
+ }
72
+
73
+ /** Mettre à jour un enregistrement. */
74
+ static async update(moduleIdentifier: string, resource: string, recordId: string, data: Record<string, any>) {
75
+ return await this.put<FetchResponseInterface<ConfigModuleRecord>>(
76
+ `/config-modules/${moduleIdentifier}/${resource}/${recordId}`,
77
+ data,
78
+ );
79
+ }
80
+
81
+ /** Supprimer (soft-delete) un enregistrement. */
82
+ static async remove(moduleIdentifier: string, resource: string, recordId: string) {
83
+ return await this.delete<FetchResponseInterface<unknown>>(
84
+ `/config-modules/${moduleIdentifier}/${resource}/${recordId}`,
85
+ );
86
+ }
87
+
88
+ /** Agrégat générique — `kpi` (scalaire) ou `chart` (groupé via `groupBy`). */
89
+ static async aggregate(moduleIdentifier: string, resource: string, payload: ConfigModuleAggregatePayload = {}) {
90
+ return await this.post<FetchResponseInterface<ConfigModuleAggregateResponse>>(
91
+ `/config-modules/${moduleIdentifier}/${resource}/_aggregate`,
92
+ payload,
93
+ );
94
+ }
95
+ }
@@ -0,0 +1,118 @@
1
+ import {ApiService} from "../../infrastructure/utilities/api-service";
2
+ import {FetchResponseInterface} from "../../domain/typing/response";
3
+ import type {
4
+ InstallModulePayloadInterface,
5
+ ModuleAssetTokenInterface,
6
+ ModuleInstallationItemInterface,
7
+ ModuleRegistryItemInterface,
8
+ ModuleResolutionResponseInterface,
9
+ ResolveModulePayloadInterface,
10
+ SyncInstallationsPayloadInterface,
11
+ SyncInstallationsResultInterface,
12
+ } from "../../domain/entities/module-installation.interface";
13
+
14
+ /**
15
+ * Service API du domaine `module-installation` (T-403).
16
+ *
17
+ * Cible `liorian-api-core` (base `/module-installation`) : résolution,
18
+ * installation, cycle de vie, sync et jetons d'artefacts.
19
+ */
20
+ export class ModuleInstallationApiService extends ApiService {
21
+
22
+ private static org(organizationId: string) {
23
+ return `/module-installation/organizations/${organizationId}`;
24
+ }
25
+
26
+ /** Étape 2 — Résoudre la version compatible (T-301). */
27
+ static async resolveModule(organizationId: string, payload: ResolveModulePayloadInterface) {
28
+ return await this.post<FetchResponseInterface<ModuleResolutionResponseInterface>>(
29
+ `${this.org(organizationId)}/resolve`,
30
+ payload,
31
+ );
32
+ }
33
+
34
+ /** Étape 5 — Installer la version résolue (jeton mono-usage, ⚠️ AdminOrAbove). */
35
+ static async installModule(organizationId: string, payload: InstallModulePayloadInterface) {
36
+ return await this.post<FetchResponseInterface<ModuleInstallationItemInterface>>(
37
+ `${this.org(organizationId)}/installations`,
38
+ payload,
39
+ );
40
+ }
41
+
42
+ /** Liste des installations (filtres `state`). */
43
+ static async listInstallations(organizationId: string, states?: string[]) {
44
+ const search = states && states.length ? `?state=${encodeURIComponent(states.join(','))}` : '';
45
+ return await this.get<FetchResponseInterface<ModuleInstallationItemInterface[]>>(
46
+ `${this.org(organizationId)}/installations${search}`,
47
+ );
48
+ }
49
+
50
+ /** Détail d'une installation. */
51
+ static async getInstallation(organizationId: string, moduleIdentifier: string) {
52
+ return await this.get<FetchResponseInterface<ModuleInstallationItemInterface>>(
53
+ `${this.org(organizationId)}/installations/${moduleIdentifier}`,
54
+ );
55
+ }
56
+
57
+ /** Registre pour le cache de rendu (ADR-006 : le core fait autorité). */
58
+ static async getRegistry(organizationId: string) {
59
+ return await this.get<FetchResponseInterface<ModuleRegistryItemInterface>>(
60
+ `${this.org(organizationId)}/installations/registry`,
61
+ );
62
+ }
63
+
64
+ /** Activer un module installé (⚠️). */
65
+ static async enableInstallation(organizationId: string, moduleIdentifier: string) {
66
+ return await this.post<FetchResponseInterface<ModuleInstallationItemInterface>>(
67
+ `${this.org(organizationId)}/installations/${moduleIdentifier}/enable`,
68
+ {},
69
+ );
70
+ }
71
+
72
+ /** Désactiver un module installé (⚠️). */
73
+ static async disableInstallation(organizationId: string, moduleIdentifier: string, reason?: string) {
74
+ return await this.post<FetchResponseInterface<ModuleInstallationItemInterface>>(
75
+ `${this.org(organizationId)}/installations/${moduleIdentifier}/disable`,
76
+ {reason},
77
+ );
78
+ }
79
+
80
+ /** Rollback vers la version précédente (⚠️). */
81
+ static async rollbackInstallation(organizationId: string, moduleIdentifier: string, targetVersion?: string) {
82
+ return await this.post<FetchResponseInterface<ModuleInstallationItemInterface>>(
83
+ `${this.org(organizationId)}/installations/${moduleIdentifier}/rollback`,
84
+ {targetVersion},
85
+ );
86
+ }
87
+
88
+ /** Désinstaller + purge disque (⚠️). */
89
+ static async uninstallInstallation(organizationId: string, moduleIdentifier: string) {
90
+ return await this.delete<FetchResponseInterface<unknown>>(
91
+ `${this.org(organizationId)}/installations/${moduleIdentifier}`,
92
+ );
93
+ }
94
+
95
+ /** Revalidation hors-ligne → en ligne. */
96
+ static async validateInstallation(organizationId: string, moduleIdentifier: string) {
97
+ return await this.post<FetchResponseInterface<ModuleInstallationItemInterface>>(
98
+ `${this.org(organizationId)}/installations/${moduleIdentifier}/validate`,
99
+ {},
100
+ );
101
+ }
102
+
103
+ /** Sync bidirectionnel (FR-023 : corrige V7). */
104
+ static async syncInstallations(organizationId: string, payload: SyncInstallationsPayloadInterface) {
105
+ return await this.post<FetchResponseInterface<SyncInstallationsResultInterface>>(
106
+ `${this.org(organizationId)}/installations/sync`,
107
+ payload,
108
+ );
109
+ }
110
+
111
+ /** Jeton d'accès iframe (TTL 24 h, T-310). */
112
+ static async issueAssetToken(organizationId: string, moduleIdentifier: string, version: string, deviceId?: string) {
113
+ return await this.post<FetchResponseInterface<ModuleAssetTokenInterface>>(
114
+ `/module-installation/artifacts/${moduleIdentifier}/${version}/assets-token`,
115
+ {deviceId},
116
+ );
117
+ }
118
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {AppConfig} from "../../domain/config/app.config";
4
4
  import {ActivityTrackerService} from "../../infrastructure/utilities/activity-tracker.service";
5
+ import {LocalDataPurgeService} from "../../infrastructure/utilities/local-data-purge.util";
5
6
  import {isTauri} from "../../infrastructure/utilities/tauri.util";
6
7
  import {NotificationsApiService} from "./notifications-api-service";
7
8
  import {NotificationsSocketService} from "./notifications-socket-service";
@@ -158,6 +159,17 @@ export class NotificationsPushService {
158
159
  notifications.forEach((notification) => this.seenNotificationIds.add(notification.id));
159
160
  }
160
161
 
162
+ /**
163
+ * Vide l'état local (notifications déjà vues) en fin de session.
164
+ *
165
+ * La clé `localStorage` est purgée par `LocalDataPurgeService` ; ce hook vide
166
+ * le `Set` en mémoire, qui se repeuplerait à la lecture — et se
167
+ * réenregistrerait — avec les notifications du compte suivant.
168
+ */
169
+ static resetSeenIds(): void {
170
+ this.seenNotificationIds = new Set<string>();
171
+ }
172
+
161
173
  protected static async poll(): Promise<void> {
162
174
  try {
163
175
  const notifications = await this.fetchUnreadNotifications();
@@ -297,3 +309,5 @@ export class NotificationsPushService {
297
309
  }
298
310
 
299
311
  }
312
+
313
+ LocalDataPurgeService.onMemoryPurge(() => NotificationsPushService.resetSeenIds());
@@ -39,6 +39,7 @@ export interface CatalogModuleInterface {
39
39
  isDefault: boolean;
40
40
  type: CatalogModuleType;
41
41
  external: boolean;
42
+ requiresActivationKey: boolean;
42
43
  categoryId?: string | null;
43
44
  category?: keyof typeof ModuleCategory | null;
44
45
  editorId?: string | null;
@@ -58,6 +59,7 @@ export interface CreateCatalogModulePayloadInterface {
58
59
  isDefault?: boolean;
59
60
  type?: CatalogModuleType;
60
61
  external?: boolean;
62
+ requiresActivationKey?: boolean;
61
63
  categoryId?: string | null;
62
64
  }
63
65
 
@@ -73,6 +75,7 @@ export interface UpdateCatalogModulePayloadInterface {
73
75
  isDefault?: boolean;
74
76
  type?: CatalogModuleType;
75
77
  external?: boolean;
78
+ requiresActivationKey?: boolean;
76
79
  categoryId?: string | null;
77
80
  }
78
81
 
@@ -3,6 +3,7 @@ export * from "./activities.interface";
3
3
  export * from "./media";
4
4
  export * from "./catalog.interface";
5
5
  export * from "./module-activation.interface";
6
+ export * from "./module-configuration.interface";
6
7
  export * from "./module.interface";
7
8
  export * from "./notification.interface";
8
9
  export * from "./objectable.type";
@@ -0,0 +1,68 @@
1
+ import type React from "react";
2
+ import type {IconKey} from "../../presentation/icons/types";
3
+ import type {ModuleDeclarationInterface} from "./module.interface";
4
+
5
+ /**
6
+ * Données partagées entre les steps du modal `ModuleConfiguration`.
7
+ */
8
+ export interface ModuleConfigurationDataInterface {
9
+ /** Clé d'activation saisie (uniquement si le module requiert une activation). */
10
+ serialKey?: string;
11
+ /** Valeurs libres collectées par les steps de configuration. */
12
+ settings: Record<string, any>;
13
+ }
14
+
15
+ /**
16
+ * Props injectées à chaque composant de step déclaré par un module.
17
+ * Le développeur doit rendre réutilisable la section/le formulaire de la page
18
+ * de paramètres correspondante en acceptant ces props.
19
+ */
20
+ export interface ModuleConfigurationStepPropsInterface<T extends Record<string, any> = Record<string, any>> {
21
+ /** Module en cours de configuration. */
22
+ module: ModuleDeclarationInterface;
23
+ /** Données courantes du stepper. */
24
+ data: Partial<T>;
25
+ /** Met à jour (merge) les données du stepper. */
26
+ updateData: (data: Partial<T>) => void;
27
+ /** Passe à l'étape suivante (déclenche les validateurs). */
28
+ next: () => void;
29
+ /** Revient à l'étape précédente. */
30
+ prev: () => void;
31
+ }
32
+
33
+ /**
34
+ * Déclaration d'une étape de configuration d'un module.
35
+ * Chaque étape correspond à une page/section du menu « Paramètres » du module.
36
+ */
37
+ export interface ModuleConfigurationStepInterface<T extends Record<string, any> = Record<string, any>> {
38
+ /** Identifiant unique de l'étape. */
39
+ id: string;
40
+ /** Titre affiché dans le stepper. */
41
+ title: string;
42
+ /** Description optionnelle affichée sous le titre. */
43
+ description?: string;
44
+ /** Icône optionnelle. */
45
+ icon?: IconKey;
46
+ /** Si vrai, la navigation valide les champs HTML `required` de l'étape. */
47
+ required?: boolean;
48
+ /** Composant React réutilisable rendant la section/le formulaire. */
49
+ component: React.ComponentType<ModuleConfigurationStepPropsInterface<T>>;
50
+ /** Validateur optionnel de l'étape (peut rediriger vers une autre étape). */
51
+ validation?: (
52
+ data: Partial<T>,
53
+ updateData: (data: Partial<T>) => void,
54
+ ) => Promise<number | string | { step: number | string; lockNavigation?: boolean } | void>;
55
+ }
56
+
57
+ /**
58
+ * Configuration initiale d'un module, exécutée avant sa première ouverture.
59
+ */
60
+ export interface ModuleConfigurationInterface {
61
+ /**
62
+ * Si vrai, une étape d'activation par clé série est ajoutée en première position
63
+ * (également affichée tant que le module n'est pas activé pour l'organisation).
64
+ */
65
+ requiresActivationKey?: boolean;
66
+ /** Étapes de configuration (une par page/section du menu Paramètres). */
67
+ steps?: ModuleConfigurationStepInterface[];
68
+ }