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.
- package/LICENSE +21 -0
- package/README.md +683 -0
- 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
|
+
}
|