syneliadev-rnpp-connect 1.0.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.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +683 -0
  3. package/package.json +38 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Synelia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,683 @@
1
+ # syneliadev-rnpp-connect
2
+
3
+ SDK officiel d'intégration **SSO RNPP Connect** (Synelia · ONECI).
4
+
5
+ Il permet à une application web d'ajouter l'authentification unique en quelques
6
+ lignes : une clé de projet suffit, la configuration (realm, client, URLs par
7
+ environnement) est déjà embarquée et gérée par Synelia.
8
+
9
+ - **API simplifiée et stable** : `init`, `login`, `getUser`, `logout`.
10
+ - **Bouton prêt à l'emploi** « Continuer avec RNPP Connect » (icône ONECI).
11
+ - **Popup avec repli automatique** en redirection pleine page.
12
+ - **Erreurs uniformes** avec des codes stables.
13
+ - **Rafraîchissement de session** automatique et **événements** d'authentification.
14
+ - **Aucune configuration sensible** côté application (ni realm, ni client-id, ni URL).
15
+
16
+ ---
17
+
18
+ ## Sommaire
19
+
20
+ 1. [Prérequis](#prérequis)
21
+ 2. [Installation](#installation)
22
+ 3. [Démarrage rapide](#démarrage-rapide)
23
+ 4. [Concepts](#concepts)
24
+ 5. [Référence API](#référence-api)
25
+ 6. [Bouton prêt à l'emploi](#bouton-prêt-à-lemploi)
26
+ 7. [Événements d'authentification](#événements-dauthentification)
27
+ 8. [Gestion des erreurs](#gestion-des-erreurs)
28
+ 9. [Intégrations par framework](#intégrations-par-framework)
29
+ 10. [Sécurité](#sécurité)
30
+ 11. [TypeScript et navigateurs](#typescript-et-navigateurs)
31
+ 12. [Dépannage / FAQ](#dépannage--faq)
32
+ 13. [Support et licence](#support-et-licence)
33
+
34
+ ---
35
+
36
+ ## Prérequis
37
+
38
+ - Une **clé de projet** fournie par Synelia (par exemple `rnpp-connect-pilot`).
39
+ - Un navigateur moderne (Chrome, Edge, Firefox, Safari — versions récentes).
40
+ - **HTTPS** en staging et production (les popups et cookies de session
41
+ l'exigent). `http://localhost` est accepté en développement.
42
+ - L'**origine de votre application** doit être enregistrée côté Synelia
43
+ (liste blanche) pour que le retour d'authentification soit autorisé.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ npm install syneliadev-rnpp-connect
49
+ ```
50
+
51
+ Fonctionne avec tout bundler moderne (Vite, Webpack, Next.js, Angular CLI,
52
+ Nuxt, SvelteKit…). Le paquet expose **ESM**, **CommonJS** et les **types
53
+ TypeScript**.
54
+
55
+ ## Démarrage rapide
56
+
57
+ ```ts
58
+ import RNPPConnect from 'syneliadev-rnpp-connect';
59
+
60
+ // 1. Initialiser (à faire une seule fois, au démarrage de l'application)
61
+ await RNPPConnect.init({ project: 'rnpp-connect-pilot' });
62
+
63
+ // 2. Authentifier l'utilisateur (popup, repli automatique en redirection)
64
+ await RNPPConnect.login();
65
+
66
+ // 3. Lire l'utilisateur connecté
67
+ const user = RNPPConnect.getUser();
68
+ console.log(user?.name, user?.email);
69
+
70
+ // 4. Se déconnecter
71
+ await RNPPConnect.logout();
72
+ ```
73
+
74
+ Avec le bouton prêt à l'emploi :
75
+
76
+ ```ts
77
+ import RNPPConnect, { mountLoginButton } from 'syneliadev-rnpp-connect';
78
+
79
+ await RNPPConnect.init({ project: 'rnpp-connect-pilot' });
80
+ mountLoginButton('#login'); // affiche « Continuer avec RNPP Connect »
81
+ ```
82
+
83
+ ```html
84
+ <div id="login"></div>
85
+ ```
86
+
87
+ ## Concepts
88
+
89
+ ### Clé de projet
90
+
91
+ Une **clé de projet** identifie votre application auprès du SSO. Elle est
92
+ fournie par Synelia et remplace toute la configuration technique (realm, client,
93
+ URLs). Votre code ne contient donc **aucune valeur sensible**.
94
+
95
+ ### Environnements
96
+
97
+ La même clé de projet fonctionne en local, staging et production. Le SDK
98
+ détecte l'environnement (`localhost` → `local`) et choisit automatiquement les
99
+ bons points d'accès. Vous pouvez le forcer avec `environment`.
100
+
101
+ ### Popup et redirection
102
+
103
+ Par défaut, `login()` ouvre l'authentification dans une **popup**. Si la popup
104
+ est bloquée par le navigateur, le SDK bascule **automatiquement** en
105
+ **redirection** pleine page : aucun code supplémentaire n'est nécessaire.
106
+
107
+ ### Page de callback (popup)
108
+
109
+ Le flux popup a besoin d'une page servie par votre application, qui termine
110
+ l'authentification et renvoie le résultat à la fenêtre principale. Par défaut,
111
+ le SDK attend `/rnpp-connect-popup-callback.html` ; il est recommandé de définir
112
+ `popupRedirectUri` vers une route de votre application (voir les exemples par
113
+ framework). Cette page appelle simplement :
114
+
115
+ ```ts
116
+ import { handlePopupCallback } from 'syneliadev-rnpp-connect';
117
+
118
+ handlePopupCallback({ project: 'rnpp-connect-pilot' });
119
+ ```
120
+
121
+ > La page de callback doit être servie sur **la même origine** que
122
+ > l'application.
123
+
124
+ ### Rafraîchissement de session
125
+
126
+ La session est rafraîchie automatiquement. En cas d'expiration, un événement
127
+ `expired` est émis et `AUTH_REQUIRED` est renvoyé : il suffit alors de relancer
128
+ `login()`.
129
+
130
+ ## Référence API
131
+
132
+ Import par défaut (instance unique, recommandée) :
133
+
134
+ ```ts
135
+ import RNPPConnect from 'syneliadev-rnpp-connect';
136
+ ```
137
+
138
+ ### Méthodes
139
+
140
+ | Méthode | Retour | Description |
141
+ | --- | --- | --- |
142
+ | `init(options)` | `Promise<boolean>` | Résout la configuration du projet et initialise la session. Renvoie `true` si un utilisateur est déjà authentifié. |
143
+ | `login(options?)` | `Promise<void>` | Authentifie l'utilisateur (popup, repli redirection). |
144
+ | `loginRedirect(options?)` | `Promise<void>` | Force le flux par redirection pleine page. |
145
+ | `loginWithPopup(options?)` | `Promise<void>` | Force le flux par popup. |
146
+ | `logout(options?)` | `Promise<void>` | Termine la session et nettoie l'état local. |
147
+ | `isAuthenticated()` | `boolean` | `true` si une session valide et non expirée existe. |
148
+ | `getUser()` | `RNPPConnectUser \| null` | Profil de l'utilisateur connecté, ou `null`. |
149
+ | `getAuthState()` | `AuthState` | État courant (sûr même avant `init`). |
150
+ | `isInitialized()` | `boolean` | Indique si le SDK a été initialisé. |
151
+ | `refresh(minValidity?)` | `Promise<boolean>` | Rafraîchit le jeton si nécessaire. |
152
+ | `onAuthChange(listener)` | `() => void` | S'abonne aux changements d'état ; retourne une fonction de désabonnement. |
153
+ | `getToken()` | `string \| undefined` | Jeton d'accès courant. |
154
+ | `getRefreshToken()` | `string \| undefined` | Jeton de rafraîchissement. |
155
+ | `getIdToken()` | `string \| undefined` | Jeton d'identité. |
156
+ | `getTokenParsed()` | `object \| undefined` | Contenu décodé du jeton d'accès. |
157
+ | `getTokens()` | `RNPPConnectTokens` | Ensemble des jetons courants. |
158
+ | `getProject()` | `{ key, displayName, environment } \| undefined` | Identité du projet (aucune donnée technique brute). |
159
+ | `handlePopupCallback(options)` | `Promise<RNPPConnectTokens>` | À utiliser dans la page de callback (import nommé). |
160
+
161
+ ### Options d'initialisation (`init`)
162
+
163
+ | Option | Type | Défaut | Description |
164
+ | --- | --- | --- | --- |
165
+ | `project` | `string` | — | **Requis.** Clé de projet fournie par Synelia. |
166
+ | `environment` | `string` | auto | `local`, `staging`, `production`. |
167
+ | `popupRedirectUri` | `string` | `/rnpp-connect-popup-callback.html` | URL de la page de callback popup. |
168
+ | `onLoad` | `'check-sso' \| 'login-required'` | `'check-sso'` | Authentifier immédiatement ou vérifier la session existante. |
169
+ | `scope` | `string` | `openid profile email` | Scopes demandés. |
170
+ | `onAuthChange` | `(state, event) => void` | — | Abonnement aux événements dès l'initialisation. |
171
+ | `registryUrl` | `string` | — | Charge la configuration depuis une URL (usage avancé). |
172
+ | `baseUrl` | `string` | — | Surcharge le point d'accès (usage avancé). |
173
+ | `checkLoginIframe` | `boolean` | `false` | Vérification silencieuse par iframe. |
174
+ | `silentCheckSsoRedirectUri` | `string` | — | URL de vérification silencieuse. |
175
+
176
+ ### Options de `login`
177
+
178
+ | Option | Type | Description |
179
+ | --- | --- | --- |
180
+ | `idp` | `string` | Cible un fournisseur d'identité externe (option avancée). |
181
+ | `redirectUri` | `string` | URL de retour après authentification. |
182
+ | `useRedirect` | `boolean` | `true` pour forcer la redirection. |
183
+
184
+ ### Options de `logout`
185
+
186
+ | Option | Type | Description |
187
+ | --- | --- | --- |
188
+ | `redirectUri` | `string` | URL de retour après déconnexion. |
189
+
190
+ ### Objet utilisateur (`RNPPConnectUser`)
191
+
192
+ ```ts
193
+ interface RNPPConnectUser {
194
+ id: string; // identifiant stable
195
+ username?: string;
196
+ email?: string;
197
+ emailVerified?: boolean;
198
+ firstName?: string;
199
+ lastName?: string;
200
+ name?: string;
201
+ roles: string[]; // rôles applicatifs
202
+ }
203
+ ```
204
+
205
+ ### Instance multiple (avancé)
206
+
207
+ Le SDK expose aussi la classe `RNPPConnectClient` pour gérer plusieurs
208
+ instances, et `setDefaultRNPPConnect(instance)` pour l'utiliser avec le bouton
209
+ prêt à l'emploi.
210
+
211
+ ## Bouton prêt à l'emploi
212
+
213
+ ```ts
214
+ import RNPPConnect, { mountLoginButton } from 'syneliadev-rnpp-connect';
215
+
216
+ await RNPPConnect.init({ project: 'rnpp-connect-pilot' });
217
+ mountLoginButton('#login'); // « Continuer avec RNPP Connect » + icône ONECI
218
+ ```
219
+
220
+ Ou directement en HTML (après `defineLoginButton()`) :
221
+
222
+ ```html
223
+ <rnpp-connect-login-button project="rnpp-connect-pilot"></rnpp-connect-login-button>
224
+ ```
225
+
226
+ | Option | Type | Défaut | Description |
227
+ | --- | --- | --- | --- |
228
+ | `project` | `string` | — | Clé de projet (si le SDK n'est pas déjà initialisé). |
229
+ | `label` | `string` | `Continuer avec RNPP Connect` | Libellé du bouton. |
230
+ | `icon` | `boolean \| string` | icône ONECI | `false` pour masquer, ou URL d'une image personnalisée. |
231
+ | `mode` | `'popup' \| 'redirect'` | `'popup'` | Flux d'authentification. |
232
+ | `provider` | `string` | — | Cible un fournisseur externe (option avancée). |
233
+ | `sso` | `RNPPConnect` | instance par défaut | Instance à utiliser. |
234
+ | `onSuccess` | `() => void` | — | Appelé après une authentification réussie. |
235
+ | `onError` | `(error) => void` | — | Appelé en cas d'échec. |
236
+
237
+ Le bouton émet aussi un événement DOM `rnpp-connect-error` (détail :
238
+ `RNPPConnectError`).
239
+
240
+ ## Événements d'authentification
241
+
242
+ `onAuthChange` notifie les changements d'état et retourne une fonction de
243
+ désabonnement. C'est le moyen recommandé de synchroniser votre interface.
244
+
245
+ ```ts
246
+ import RNPPConnect, { type AuthState, type AuthChangeEvent } from 'syneliadev-rnpp-connect';
247
+
248
+ const unsubscribe = RNPPConnect.onAuthChange((state: AuthState, event: AuthChangeEvent) => {
249
+ // event: 'init' | 'login' | 'logout' | 'refresh' | 'expired'
250
+ console.log(event, state.authenticated, state.user);
251
+ });
252
+
253
+ // plus tard
254
+ unsubscribe();
255
+ ```
256
+
257
+ - `getAuthState()` lit l'état courant à tout moment.
258
+ - `expired` signale une session à réauthentifier (`AUTH_REQUIRED`).
259
+
260
+ ## Gestion des erreurs
261
+
262
+ Toute défaillance rejette une `RNPPConnectError` exposant un `code` stable et
263
+ un `message` lisible.
264
+
265
+ | Code | Signification | Traitement conseillé |
266
+ | --- | --- | --- |
267
+ | `CONFIG_INVALID` | Configuration invalide | Corriger la configuration, ne pas réessayer. |
268
+ | `CONFIG_UNKNOWN_PROJECT` | Clé de projet inconnue | Vérifier la clé fournie par Synelia. |
269
+ | `CONFIG_ENVIRONMENT_UNKNOWN` | Environnement inconnu | Vérifier `environment`. |
270
+ | `NOT_INITIALIZED` | API appelée avant `init` | Appeler `init` au démarrage. |
271
+ | `INIT_FAILED` | Échec d'initialisation | Vérifier le réseau. |
272
+ | `LOGIN_FAILED` | Échec d'authentification | Afficher un message, proposer de réessayer. |
273
+ | `LOGIN_CANCELLED` | Popup fermée par l'utilisateur | Ignorer silencieusement. |
274
+ | `LOGIN_POPUP_BLOCKED` | Popup bloquée (repli effectué) | Informationnel. |
275
+ | `LOGOUT_FAILED` | Échec de déconnexion | Nettoyer l'état local, réessayer. |
276
+ | `TOKEN_REFRESH_FAILED` | Rafraîchissement échoué | Redemander une authentification. |
277
+ | `AUTH_REQUIRED` | Session expirée | Relancer `login()`. |
278
+ | `NETWORK_ERROR` | Réseau indisponible | Réessayer avec un délai progressif. |
279
+
280
+ ```ts
281
+ import { RNPPConnectError } from 'syneliadev-rnpp-connect';
282
+
283
+ try {
284
+ await RNPPConnect.login();
285
+ } catch (error) {
286
+ if (error instanceof RNPPConnectError) {
287
+ if (error.code === 'LOGIN_CANCELLED') return; // fermée par l'utilisateur
288
+ if (error.code === 'AUTH_REQUIRED') return RNPPConnect.login();
289
+ }
290
+ throw error;
291
+ }
292
+ ```
293
+
294
+ ## Intégrations par framework
295
+
296
+ > Dans tous les exemples, remplacez `VOTRE_CLE` par la clé de projet fournie
297
+ > par Synelia, et enregistrez l'origine de votre application côté Synelia.
298
+
299
+ ### React (Vite)
300
+
301
+ ```tsx
302
+ // src/main.tsx
303
+ import { StrictMode } from 'react';
304
+ import { createRoot } from 'react-dom/client';
305
+ import RNPPConnect from 'syneliadev-rnpp-connect';
306
+ import App from './App';
307
+
308
+ async function bootstrap() {
309
+ await RNPPConnect.init({
310
+ project: 'VOTRE_CLE',
311
+ popupRedirectUri: `${window.location.origin}/callback`,
312
+ });
313
+ createRoot(document.getElementById('root')!).render(
314
+ <StrictMode>
315
+ <App />
316
+ </StrictMode>,
317
+ );
318
+ }
319
+ void bootstrap();
320
+ ```
321
+
322
+ ```tsx
323
+ // src/App.tsx
324
+ import { useEffect, useRef, useState } from 'react';
325
+ import RNPPConnect, { mountLoginButton } from 'syneliadev-rnpp-connect';
326
+
327
+ export default function App() {
328
+ const host = useRef<HTMLDivElement>(null);
329
+ const [authenticated, setAuthenticated] = useState(false);
330
+
331
+ useEffect(() => {
332
+ const unsubscribe = RNPPConnect.onAuthChange((state) => setAuthenticated(state.authenticated));
333
+ return unsubscribe;
334
+ }, []);
335
+
336
+ useEffect(() => {
337
+ if (!host.current) return;
338
+ const button = mountLoginButton(host.current);
339
+ return () => button.remove();
340
+ }, []);
341
+
342
+ if (authenticated) {
343
+ const user = RNPPConnect.getUser();
344
+ return (
345
+ <>
346
+ <p>Bonjour {user?.name ?? user?.username}</p>
347
+ <button onClick={() => RNPPConnect.logout()}>Se déconnecter</button>
348
+ </>
349
+ );
350
+ }
351
+
352
+ return <div ref={host} />;
353
+ }
354
+ ```
355
+
356
+ Page de callback (`src/Callback.tsx`, montée sur la route `/callback`) :
357
+
358
+ ```tsx
359
+ import { useEffect } from 'react';
360
+ import { handlePopupCallback } from 'syneliadev-rnpp-connect';
361
+
362
+ export default function Callback() {
363
+ useEffect(() => {
364
+ handlePopupCallback({ project: 'VOTRE_CLE' }).catch(() => window.close());
365
+ }, []);
366
+ return <p>Authentification en cours…</p>;
367
+ }
368
+ ```
369
+
370
+ ### Next.js (App Router)
371
+
372
+ Le SDK s'exécute dans le navigateur : utilisez des composants clients.
373
+
374
+ ```tsx
375
+ // app/providers.tsx
376
+ 'use client';
377
+
378
+ import { useEffect, useRef } from 'react';
379
+ import RNPPConnect, { mountLoginButton } from 'syneliadev-rnpp-connect';
380
+
381
+ export function SsoButton() {
382
+ const host = useRef<HTMLDivElement>(null);
383
+
384
+ useEffect(() => {
385
+ void RNPPConnect.init({
386
+ project: 'VOTRE_CLE',
387
+ popupRedirectUri: `${window.location.origin}/callback`,
388
+ });
389
+ if (!host.current) return;
390
+ const button = mountLoginButton(host.current);
391
+ return () => button.remove();
392
+ }, []);
393
+
394
+ return <div ref={host} />;
395
+ }
396
+ ```
397
+
398
+ ```tsx
399
+ // app/callback/page.tsx
400
+ 'use client';
401
+
402
+ import { useEffect } from 'react';
403
+ import { handlePopupCallback } from 'syneliadev-rnpp-connect';
404
+
405
+ export default function CallbackPage() {
406
+ useEffect(() => {
407
+ handlePopupCallback({ project: 'VOTRE_CLE' }).catch(() => window.close());
408
+ }, []);
409
+ return <p>Authentification en cours…</p>;
410
+ }
411
+ ```
412
+
413
+ > Pour les composants rendus côté serveur, importez le SDK uniquement dans des
414
+ > composants `'use client'` (ou via `next/dynamic` avec `{ ssr: false }`).
415
+
416
+ ### Vue 3 (Vite)
417
+
418
+ ```ts
419
+ // src/main.ts
420
+ import { createApp } from 'vue';
421
+ import RNPPConnect from 'syneliadev-rnpp-connect';
422
+ import App from './App.vue';
423
+ import router from './router';
424
+
425
+ await RNPPConnect.init({
426
+ project: 'VOTRE_CLE',
427
+ popupRedirectUri: `${window.location.origin}/callback`,
428
+ });
429
+
430
+ createApp(App).use(router).mount('#app');
431
+ ```
432
+
433
+ ```vue
434
+ <!-- src/App.vue -->
435
+ <script setup lang="ts">
436
+ import { onMounted, onUnmounted, ref } from 'vue';
437
+ import RNPPConnect, { mountLoginButton } from 'syneliadev-rnpp-connect';
438
+
439
+ const host = ref<HTMLDivElement>();
440
+ const authenticated = ref(false);
441
+ let unsubscribe: (() => void) | undefined;
442
+ let button: { remove: () => void } | undefined;
443
+
444
+ onMounted(() => {
445
+ unsubscribe = RNPPConnect.onAuthChange((state) => (authenticated.value = state.authenticated));
446
+ if (host.value) button = mountLoginButton(host.value);
447
+ });
448
+ onUnmounted(() => {
449
+ unsubscribe?.();
450
+ button?.remove();
451
+ });
452
+ </script>
453
+
454
+ <template>
455
+ <div v-if="!authenticated" ref="host" />
456
+ <div v-else>
457
+ <p>Bonjour {{ RNPPConnect.getUser()?.name }}</p>
458
+ <button @click="RNPPConnect.logout()">Se déconnecter</button>
459
+ </div>
460
+ </template>
461
+ ```
462
+
463
+ Route `/callback` : un composant qui appelle
464
+ `handlePopupCallback({ project: 'VOTRE_CLE' })` dans `onMounted`.
465
+
466
+ ### Nuxt 3
467
+
468
+ ```ts
469
+ // plugins/rnpp-connect.client.ts
470
+ import RNPPConnect from 'syneliadev-rnpp-connect';
471
+
472
+ export default defineNuxtPlugin(async () => {
473
+ await RNPPConnect.init({
474
+ project: 'VOTRE_CLE',
475
+ popupRedirectUri: `${window.location.origin}/callback`,
476
+ });
477
+ return { provide: { sso: RNPPConnect } };
478
+ });
479
+ ```
480
+
481
+ ```vue
482
+ <!-- pages/callback.vue -->
483
+ <script setup lang="ts">
484
+ import { handlePopupCallback } from 'syneliadev-rnpp-connect';
485
+
486
+ onMounted(() => {
487
+ handlePopupCallback({ project: 'VOTRE_CLE' }).catch(() => window.close());
488
+ });
489
+ </script>
490
+
491
+ <template>
492
+ <p>Authentification en cours…</p>
493
+ </template>
494
+ ```
495
+
496
+ Dans une page, récupérez l'instance via `const { $sso } = useNuxtApp()` et
497
+ montez le bouton avec `mountLoginButton`.
498
+
499
+ ### Angular
500
+
501
+ ```ts
502
+ // src/app/sso.service.ts
503
+ import { Injectable } from '@angular/core';
504
+ import RNPPConnect from 'syneliadev-rnpp-connect';
505
+
506
+ @Injectable({ providedIn: 'root' })
507
+ export class SsoService {
508
+ init(): Promise<boolean> {
509
+ return RNPPConnect.init({
510
+ project: 'VOTRE_CLE',
511
+ popupRedirectUri: `${window.location.origin}/callback`,
512
+ });
513
+ }
514
+ readonly sso = RNPPConnect;
515
+ }
516
+ ```
517
+
518
+ ```ts
519
+ // src/main.ts
520
+ import { bootstrapApplication } from '@angular/platform-browser';
521
+ import { AppComponent } from './app/app.component';
522
+ import { appConfig } from './app/app.config';
523
+ import { SsoService } from './app/sso.service';
524
+
525
+ async function bootstrap() {
526
+ await new SsoService().init();
527
+ await bootstrapApplication(AppComponent, appConfig);
528
+ }
529
+ void bootstrap();
530
+ ```
531
+
532
+ ```ts
533
+ // composant avec le bouton
534
+ import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
535
+ import { mountLoginButton, type RNPPConnectLoginButton } from 'syneliadev-rnpp-connect';
536
+ import { SsoService } from './sso.service';
537
+
538
+ @Component({ selector: 'app-login', standalone: true, template: '<div #host></div>' })
539
+ export class LoginComponent implements OnInit, OnDestroy {
540
+ @ViewChild('host', { static: true }) host!: ElementRef<HTMLDivElement>;
541
+ private button?: RNPPConnectLoginButton;
542
+
543
+ constructor(private readonly ssoService: SsoService) {}
544
+
545
+ ngOnInit(): void {
546
+ this.button = mountLoginButton(this.host.nativeElement, { sso: this.ssoService.sso });
547
+ }
548
+ ngOnDestroy(): void {
549
+ this.button?.remove();
550
+ }
551
+ }
552
+ ```
553
+
554
+ Route `/callback` : un composant qui appelle
555
+ `handlePopupCallback({ project: 'VOTRE_CLE' })` dans `ngOnInit`.
556
+
557
+ ### Svelte / SvelteKit
558
+
559
+ ```svelte
560
+ <!-- src/routes/+layout.svelte -->
561
+ <script lang="ts">
562
+ import { onMount } from 'svelte';
563
+ import RNPPConnect, { mountLoginButton } from 'syneliadev-rnpp-connect';
564
+
565
+ let host: HTMLDivElement;
566
+
567
+ onMount(() => {
568
+ void RNPPConnect.init({
569
+ project: 'VOTRE_CLE',
570
+ popupRedirectUri: `${window.location.origin}/callback`,
571
+ });
572
+ const button = mountLoginButton(host);
573
+ return () => button.remove();
574
+ });
575
+ </script>
576
+
577
+ <div bind:this={host} />
578
+ ```
579
+
580
+ ```svelte
581
+ <!-- src/routes/callback/+page.svelte -->
582
+ <script lang="ts">
583
+ import { onMount } from 'svelte';
584
+ import { handlePopupCallback } from 'syneliadev-rnpp-connect';
585
+
586
+ onMount(() => {
587
+ handlePopupCallback({ project: 'VOTRE_CLE' }).catch(() => window.close());
588
+ });
589
+ </script>
590
+
591
+ <p>Authentification en cours…</p>
592
+ ```
593
+
594
+ ### HTML / JavaScript (sans framework)
595
+
596
+ ```html
597
+ <!doctype html>
598
+ <html lang="fr">
599
+ <body>
600
+ <div id="login"></div>
601
+ <pre id="user"></pre>
602
+
603
+ <script type="module">
604
+ import RNPPConnect, { mountLoginButton } from 'syneliadev-rnpp-connect';
605
+
606
+ await RNPPConnect.init({
607
+ project: 'VOTRE_CLE',
608
+ popupRedirectUri: `${window.location.origin}/rnpp-connect-popup-callback.html`,
609
+ });
610
+ mountLoginButton('#login');
611
+ document.getElementById('user').textContent = JSON.stringify(RNPPConnect.getUser(), null, 2);
612
+ </script>
613
+ </body>
614
+ </html>
615
+ ```
616
+
617
+ Page `rnpp-connect-popup-callback.html` :
618
+
619
+ ```html
620
+ <!doctype html>
621
+ <html lang="fr">
622
+ <body>
623
+ <p>Authentification en cours…</p>
624
+ <script type="module">
625
+ import { handlePopupCallback } from 'syneliadev-rnpp-connect';
626
+ handlePopupCallback({ project: 'VOTRE_CLE' }).catch(() => window.close());
627
+ </script>
628
+ </body>
629
+ </html>
630
+ ```
631
+
632
+ ## Sécurité
633
+
634
+ - Les jetons sont conservés **en mémoire** et ne sont pas persistés dans
635
+ `localStorage` par défaut.
636
+ - **Ne journalisez jamais** les jetons et ne les transmettez pas hors de votre
637
+ domaine.
638
+ - Utilisez **HTTPS** en staging et production.
639
+ - Enregistrez uniquement les **origines nécessaires** côté Synelia.
640
+ - Appelez `logout()` pour terminer proprement la session.
641
+
642
+ ## TypeScript et navigateurs
643
+
644
+ - Types inclus (aucun `@types` supplémentaire).
645
+ - ESM, CommonJS et définitions `.d.ts`.
646
+ - Navigateurs modernes : Chrome, Edge, Firefox, Safari (2 dernières versions
647
+ majeures).
648
+
649
+ Types principaux exportés : `RNPPConnectUser`, `RNPPConnectTokens`,
650
+ `RNPPConnectInitOptions`, `RNPPConnectLoginOptions`, `RNPPConnectLogoutOptions`,
651
+ `AuthState`, `AuthChangeEvent`, `AuthChangeListener`, `RNPPConnectError`,
652
+ `LoginButtonOptions`.
653
+
654
+ ## Dépannage / FAQ
655
+
656
+ **Le bouton ne fait rien / `NOT_INITIALIZED`.**
657
+ Appelez `await RNPPConnect.init({ project })` au démarrage, avant d'utiliser le
658
+ bouton ou `login()`.
659
+
660
+ **La popup est bloquée.**
661
+ Le SDK bascule automatiquement en redirection. Pour forcer ce comportement,
662
+ utilisez `mountLoginButton('#login', { mode: 'redirect' })` ou
663
+ `login({ useRedirect: true })`.
664
+
665
+ **`Invalid parameter: redirect_uri`.**
666
+ L'origine ou l'URL de retour n'est pas autorisée : vérifiez qu'elle est bien
667
+ enregistrée côté Synelia.
668
+
669
+ **Je reste « déconnecté » après le retour d'authentification.**
670
+ Assurez-vous d'attendre la fin de `init()` avant de lire l'état, et abonnez-vous
671
+ à `onAuthChange` pour mettre à jour l'interface automatiquement.
672
+
673
+ **`AUTH_REQUIRED` après un moment.**
674
+ La session a expiré : relancez `login()`.
675
+
676
+ **Les libellés / l'écran de connexion.**
677
+ L'écran d'authentification RNPP Connect (ONECI) est fourni par Synelia ; sa
678
+ personnalisation par application est gérée côté plateforme.
679
+
680
+ ## Support et licence
681
+
682
+ - **Support** : saintcyrwin@gmail.com — https://synelia.tech
683
+ - **Licence** : [MIT](./LICENSE) © 2026 Synelia
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "syneliadev-rnpp-connect",
3
+ "version": "1.0.0",
4
+ "description": "SDK officiel d'intégration SSO RNPP Connect (Synelia / ONECI).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": ["dist", "README.md", "LICENSE"],
18
+ "sideEffects": false,
19
+ "publishConfig": {
20
+ "registry": "https://registry.npmjs.org/",
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "vitest run",
27
+ "verify": "npm run typecheck && npm run test && npm run build"
28
+ },
29
+ "dependencies": {
30
+ "keycloak-js": "^26.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "jsdom": "^25.0.1",
34
+ "tsup": "^8.3.5",
35
+ "typescript": "^5.6.3",
36
+ "vitest": "^2.1.8"
37
+ }
38
+ }