@shane_donnelly/dsi-internal-react-utils 0.0.2 → 0.1.1

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/README.md CHANGED
@@ -1,4 +1,203 @@
1
- ### Librairie de composants React pour les différents projet de la DSI
1
+ # @shane_donnelly/dsi-internal-react-utils
2
2
 
3
- Actuellement, les composants sont des exemples.
3
+ Librairie de composants et utilitaires React pour les projets front-end de la DSI.
4
4
 
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @shane_donnelly/dsi-internal-react-utils keycloak-js
9
+ ```
10
+
11
+ ### Prérequis
12
+
13
+ - Un serveur Keycloak configuré avec un realm, un client, et au moins un Identity Provider
14
+
15
+ > **Recommandé** : utiliser [react-router](https://reactrouter.com/) pour éviter de perdre l'état de la page lors des redirections d'authentification.
16
+
17
+ ## Modules
18
+
19
+ ### Keycloak
20
+
21
+ Wrapper de simplification pour `keycloak-js`. Gère l'authentification automatique via Identity Provider, le refresh de token, et la protection de routes/zones.
22
+
23
+ [Documentation complète du module Keycloak](docs/keycloak.md)
24
+
25
+ ---
26
+
27
+ ## Exemple complet avec React Router
28
+
29
+ ```tsx
30
+ // main.tsx
31
+ import React from 'react';
32
+ import ReactDOM from 'react-dom/client';
33
+ import { BrowserRouter } from 'react-router-dom';
34
+ import App from './App';
35
+
36
+ ReactDOM.createRoot(document.getElementById('root')!).render(
37
+ <React.StrictMode>
38
+ <BrowserRouter>
39
+ <App />
40
+ </BrowserRouter>
41
+ </React.StrictMode>,
42
+ );
43
+ ```
44
+
45
+ ```tsx
46
+ // App.tsx
47
+ import { Routes, Route } from 'react-router-dom';
48
+ import { KeycloakProvider, ProtectedRoute } from '@shane_donnelly/dsi-internal-react-utils';
49
+ import PublicPage from './pages/PublicPage';
50
+ import Dashboard from './pages/Dashboard';
51
+ import Profile from './pages/Profile';
52
+
53
+ const keycloakConfig = {
54
+ url: 'https://keycloak.example.com',
55
+ realm: 'my-realm',
56
+ clientId: 'my-frontend',
57
+ };
58
+
59
+ export default function App() {
60
+ return (
61
+ <KeycloakProvider config={keycloakConfig} idpHint="oidc" refreshInterval={300}>
62
+ <Routes>
63
+ {/* Route publique — accessible sans authentification */}
64
+ <Route path="/" element={<PublicPage />} />
65
+
66
+ {/* Routes protégées — redirection automatique vers l'IDP */}
67
+ <Route
68
+ path="/dashboard"
69
+ element={
70
+ <ProtectedRoute>
71
+ <Dashboard />
72
+ </ProtectedRoute>
73
+ }
74
+ />
75
+ <Route
76
+ path="/profile"
77
+ element={
78
+ <ProtectedRoute fallback={<p>Chargement du profil...</p>}>
79
+ <Profile />
80
+ </ProtectedRoute>
81
+ }
82
+ />
83
+ </Routes>
84
+ </KeycloakProvider>
85
+ );
86
+ }
87
+ ```
88
+
89
+ ```tsx
90
+ // pages/Dashboard.tsx
91
+ import { useAuth } from '@shane_donnelly/dsi-internal-react-utils';
92
+
93
+ export default function Dashboard() {
94
+ const { user, token, logout } = useAuth();
95
+
96
+ return (
97
+ <div>
98
+ <h1>Bienvenue {user?.name}</h1>
99
+ <p>Email : {user?.email}</p>
100
+ <button onClick={() => logout()}>Se déconnecter</button>
101
+ </div>
102
+ );
103
+ }
104
+ ```
105
+
106
+ ### Mode plug & play (sans Provider)
107
+
108
+ Pour un usage rapide sans `KeycloakProvider`, passez `config` directement à `ProtectedRoute` :
109
+
110
+ ```tsx
111
+ import { ProtectedRoute } from '@shane_donnelly/dsi-internal-react-utils';
112
+
113
+ function App() {
114
+ return (
115
+ <ProtectedRoute
116
+ config={{
117
+ url: 'https://keycloak.example.com',
118
+ realm: 'my-realm',
119
+ clientId: 'my-frontend',
120
+ }}
121
+ idpHint="oidc"
122
+ >
123
+ <Dashboard />
124
+ </ProtectedRoute>
125
+ );
126
+ }
127
+ ```
128
+
129
+ ### Utiliser le token pour les appels API
130
+
131
+ ```tsx
132
+ import { useAuth } from '@shane_donnelly/dsi-internal-react-utils';
133
+
134
+ function useAuthFetch() {
135
+ const { token } = useAuth();
136
+
137
+ return (url: string, options?: RequestInit) =>
138
+ fetch(url, {
139
+ ...options,
140
+ headers: {
141
+ ...options?.headers,
142
+ Authorization: `Bearer ${token}`,
143
+ },
144
+ });
145
+ }
146
+ ```
147
+
148
+ ### Accéder à l'instance keycloak-js (usage avancé)
149
+
150
+ ```tsx
151
+ import { useAuth } from '@shane_donnelly/dsi-internal-react-utils';
152
+
153
+ function AdvancedComponent() {
154
+ const { keycloak } = useAuth();
155
+
156
+ // Accès direct à l'instance keycloak-js pour des cas spécifiques
157
+ console.log(keycloak?.tokenParsed);
158
+ }
159
+ ```
160
+
161
+ ### Fonctions utilitaires standalone
162
+
163
+ Deux fonctions sont disponibles pour manipuler l'instance Keycloak en dehors de React (intercepteurs HTTP, services, etc.) :
164
+
165
+ ```tsx
166
+ import { useAuth, logoutKeycloak, refreshTokenKeycloak } from '@shane_donnelly/dsi-internal-react-utils';
167
+
168
+ // Récupérer l'instance keycloak via useAuth()
169
+ const { keycloak } = useAuth();
170
+
171
+ // Rafraîchir le token (minValidity en secondes, défaut: 30)
172
+ const refreshed = await refreshTokenKeycloak(keycloak!, 60);
173
+ const freshToken = keycloak!.token;
174
+
175
+ // Déconnecter l'utilisateur (redirectUri optionnel, défaut: window.location.origin)
176
+ await logoutKeycloak(keycloak!);
177
+ ```
178
+
179
+ Exemple dans un intercepteur Axios :
180
+
181
+ ```ts
182
+ import axios from 'axios';
183
+ import { logoutKeycloak, refreshTokenKeycloak } from '@shane_donnelly/dsi-internal-react-utils';
184
+ import type Keycloak from 'keycloak-js';
185
+
186
+ function setupInterceptors(keycloak: Keycloak) {
187
+ axios.interceptors.request.use(async (config) => {
188
+ await refreshTokenKeycloak(keycloak, 60);
189
+ config.headers.Authorization = `Bearer ${keycloak.token}`;
190
+ return config;
191
+ });
192
+
193
+ axios.interceptors.response.use(
194
+ (response) => response,
195
+ async (error) => {
196
+ if (error.response?.status === 401) {
197
+ await logoutKeycloak(keycloak);
198
+ }
199
+ return Promise.reject(error);
200
+ },
201
+ );
202
+ }
203
+ ```
@@ -0,0 +1,55 @@
1
+ import { KeycloakProvider as e } from "./keycloak/react/KeycloakProvider/index.js";
2
+ import { useAuth as t } from "./keycloak/react/hooks/useAuth.js";
3
+ import { useEffect as n, useRef as r } from "react";
4
+ import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
5
+ import './assets/ProtectedRoute.css';var s = {
6
+ loadingContainer: "_loadingContainer_1th3z_1",
7
+ spinner: "_spinner_1th3z_11",
8
+ spin: "_spin_1th3z_11",
9
+ errorContainer: "_errorContainer_1th3z_26",
10
+ errorMessage: "_errorMessage_1th3z_36"
11
+ };
12
+ //#endregion
13
+ //#region lib/keycloak/react/ProtectedRoute/index.tsx
14
+ function c() {
15
+ return /* @__PURE__ */ o("div", {
16
+ className: s.loadingContainer,
17
+ children: [/* @__PURE__ */ a("div", { className: s.spinner }), /* @__PURE__ */ a("p", { children: "Authentication in progress..." })]
18
+ });
19
+ }
20
+ function l() {
21
+ return /* @__PURE__ */ a("div", {
22
+ className: s.errorContainer,
23
+ children: /* @__PURE__ */ a("p", {
24
+ className: s.errorMessage,
25
+ children: "Authentication error. Please try again later."
26
+ })
27
+ });
28
+ }
29
+ function u({ children: e, fallback: o, errorFallback: s }) {
30
+ let { status: u, login: d } = t(), f = r(!1);
31
+ return n(() => {
32
+ u === "unauthenticated" && !f.current && (f.current = !0, d().catch((e) => {
33
+ console.error("[dsi-keycloak] Login redirect failed:", e);
34
+ }));
35
+ }, [u, d]), u === "loading" || u === "unauthenticated" ? /* @__PURE__ */ a(i, { children: o ?? /* @__PURE__ */ a(c, {}) }) : u === "error" ? /* @__PURE__ */ a(i, { children: s ?? /* @__PURE__ */ a(l, {}) }) : /* @__PURE__ */ a(i, { children: e });
36
+ }
37
+ function d(e) {
38
+ return "config" in e && e.config != null;
39
+ }
40
+ function f(t) {
41
+ if (d(t)) {
42
+ let { config: n, idpHint: r, refreshInterval: i, minTokenValidity: o, onAuthError: s, ...c } = t;
43
+ return /* @__PURE__ */ a(e, {
44
+ config: n,
45
+ idpHint: r,
46
+ refreshInterval: i,
47
+ minTokenValidity: o,
48
+ onAuthError: s,
49
+ children: /* @__PURE__ */ a(u, { ...c })
50
+ });
51
+ }
52
+ return /* @__PURE__ */ a(u, { ...t });
53
+ }
54
+ //#endregion
55
+ export { f as t };
@@ -0,0 +1 @@
1
+ ._loadingContainer_1th3z_1{flex-direction:column;justify-content:center;align-items:center;gap:1rem;min-height:100vh;font-family:system-ui,-apple-system,sans-serif;display:flex}._spinner_1th3z_11{border:3px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;width:40px;height:40px;animation:1s linear infinite _spin_1th3z_11}@keyframes _spin_1th3z_11{to{transform:rotate(360deg)}}._errorContainer_1th3z_26{flex-direction:column;justify-content:center;align-items:center;gap:1rem;min-height:100vh;font-family:system-ui,-apple-system,sans-serif;display:flex}._errorMessage_1th3z_36{color:#dc2626;text-align:center;background:#fef2f2;border-radius:12px;max-width:400px;padding:1.5rem}
@@ -0,0 +1,60 @@
1
+ import { default as Keycloak } from 'keycloak-js';
2
+ import { KeycloakConfig, AuthUser } from './types';
3
+ /**
4
+ * Crée une nouvelle instance keycloak-js à partir de la configuration.
5
+ * @param config - Configuration du serveur Keycloak
6
+ * @returns Instance Keycloak
7
+ */
8
+ export declare function createKeycloakInstance(config: KeycloakConfig): Keycloak;
9
+ /**
10
+ * Initialise une instance Keycloak avec `check-sso` et PKCE S256.
11
+ * Détecte automatiquement une session SSO existante sans forcer le login.
12
+ *
13
+ * @param keycloak - Instance Keycloak à initialiser
14
+ * @returns `true` si l'utilisateur est déjà authentifié
15
+ */
16
+ export declare function initKeycloak(keycloak: Keycloak): Promise<boolean>;
17
+ /**
18
+ * Extrait les informations utilisateur du token parsé Keycloak.
19
+ * @param keycloak - Instance Keycloak
20
+ * @returns Informations utilisateur ou `null`
21
+ */
22
+ export declare function parseUser(keycloak: Keycloak): AuthUser | null;
23
+ /**
24
+ * Déconnecte l'utilisateur via l'instance Keycloak.
25
+ *
26
+ * Utilisable en dehors de React (ex: intercepteur HTTP, service, guard).
27
+ *
28
+ * @param keycloak - Instance Keycloak (récupérable via `useAuth().keycloak`)
29
+ * @param redirectUri - URL de redirection après déconnexion (défaut: `window.location.origin`)
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { logoutKeycloak } from '@shane_donnelly/dsi-internal-react-utils';
34
+ *
35
+ * // dans un intercepteur axios
36
+ * if (response.status === 401) {
37
+ * await logoutKeycloak(keycloakInstance);
38
+ * }
39
+ * ```
40
+ */
41
+ export declare function logoutKeycloak(keycloak: Keycloak, redirectUri?: string): Promise<void>;
42
+ /**
43
+ * Rafraîchit le token d'accès via l'instance Keycloak.
44
+ *
45
+ * Utilisable en dehors de React (ex: intercepteur HTTP, service).
46
+ *
47
+ * @param keycloak - Instance Keycloak (récupérable via `useAuth().keycloak`)
48
+ * @param minValidity - Durée minimale de validité restante en secondes (défaut: 30)
49
+ * @returns `true` si le token a été rafraîchi, `false` sinon
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * import { refreshTokenKeycloak } from '@shane_donnelly/dsi-internal-react-utils';
54
+ *
55
+ * // avant un appel API
56
+ * await refreshTokenKeycloak(keycloakInstance, 60);
57
+ * const token = keycloakInstance.token;
58
+ * ```
59
+ */
60
+ export declare function refreshTokenKeycloak(keycloak: Keycloak, minValidity?: number): Promise<boolean>;
@@ -0,0 +1,35 @@
1
+ import e from "keycloak-js";
2
+ //#region lib/keycloak/core/client.ts
3
+ function t(t) {
4
+ return new e({
5
+ url: t.url,
6
+ realm: t.realm,
7
+ clientId: t.clientId
8
+ });
9
+ }
10
+ async function n(e) {
11
+ try {
12
+ return await e.init({
13
+ onLoad: "check-sso",
14
+ pkceMethod: "S256",
15
+ checkLoginIframe: !1
16
+ });
17
+ } catch (e) {
18
+ return console.error("[dsi-keycloak] Init failed:", e), !1;
19
+ }
20
+ }
21
+ function r(e) {
22
+ return e.tokenParsed ? e.tokenParsed : null;
23
+ }
24
+ async function i(e, t) {
25
+ await e.logout({ redirectUri: t ?? window.location.origin });
26
+ }
27
+ async function a(e, t = 30) {
28
+ try {
29
+ return await e.updateToken(t);
30
+ } catch (e) {
31
+ return console.warn("[dsi-keycloak] Token refresh failed:", e), !1;
32
+ }
33
+ }
34
+ //#endregion
35
+ export { t as createKeycloakInstance, n as initKeycloak, i as logoutKeycloak, r as parseUser, a as refreshTokenKeycloak };
@@ -0,0 +1,67 @@
1
+ import { default as Keycloak } from 'keycloak-js';
2
+ /**
3
+ * Configuration du serveur Keycloak.
4
+ */
5
+ export type KeycloakConfig = {
6
+ /** URL du serveur Keycloak (ex: "https://keycloak.example.com") */
7
+ url: string;
8
+ /** Nom du realm Keycloak */
9
+ realm: string;
10
+ /** Client ID de l'application */
11
+ clientId: string;
12
+ };
13
+ /**
14
+ * Options d'authentification pour le module Keycloak.
15
+ */
16
+ export type KeycloakAuthOptions = {
17
+ /** Hint d'identity provider pour redirection directe (ex: "oidc", "microsoft", "google") */
18
+ idpHint?: string;
19
+ /** Intervalle de rafraîchissement du token en secondes (défaut: 300 = 5min) */
20
+ refreshInterval?: number;
21
+ /** Validité minimale du token en secondes avant rafraîchissement (défaut: 30) */
22
+ minTokenValidity?: number;
23
+ /** Comportement en cas d'erreur d'auth: 'login' pour rediriger, 'logout' pour déconnecter, ou un handler custom */
24
+ onAuthError?: 'login' | 'logout' | ((error: unknown) => void);
25
+ };
26
+ /**
27
+ * État de l'authentification.
28
+ * - `loading` : vérification en cours
29
+ * - `authenticated` : utilisateur connecté
30
+ * - `unauthenticated` : utilisateur non connecté
31
+ * - `error` : erreur lors de l'initialisation
32
+ */
33
+ export type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated' | 'error';
34
+ /**
35
+ * Informations utilisateur extraites du token Keycloak.
36
+ * Contient les claims standard OpenID Connect + toute claim custom.
37
+ */
38
+ export type AuthUser = {
39
+ sub?: string;
40
+ name?: string;
41
+ email?: string;
42
+ preferred_username?: string;
43
+ given_name?: string;
44
+ family_name?: string;
45
+ [key: string]: unknown;
46
+ };
47
+ /**
48
+ * Valeur du contexte d'authentification exposée par `KeycloakProvider` et `useAuth`.
49
+ */
50
+ export type AuthContextValue = {
51
+ /** État actuel de l'authentification */
52
+ status: AuthStatus;
53
+ /** Token d'accès brut (null si non authentifié) */
54
+ token: string | null;
55
+ /** Informations utilisateur extraites du token */
56
+ user: AuthUser | null;
57
+ /** Raccourci pour `status === 'authenticated'` */
58
+ isAuthenticated: boolean;
59
+ /** Instance keycloak-js sous-jacente (pour usage avancé) */
60
+ keycloak: Keycloak | null;
61
+ /** Déclencher une redirection de login. Accepte un idpHint optionnel pour override celui du Provider. */
62
+ login: (idpHint?: string) => Promise<void>;
63
+ /** Déclencher un logout. Accepte un redirectUri optionnel (défaut: origin). */
64
+ logout: (redirectUri?: string) => Promise<void>;
65
+ /** Rafraîchir manuellement le token. Retourne true si réussi. */
66
+ refreshToken: () => Promise<boolean>;
67
+ };
File without changes
@@ -0,0 +1,7 @@
1
+ export { KeycloakProvider } from './react/KeycloakProvider';
2
+ export type { KeycloakProviderProps } from './react/KeycloakProvider';
3
+ export { ProtectedRoute } from './react/ProtectedRoute';
4
+ export type { ProtectedRouteProps } from './react/ProtectedRoute';
5
+ export { useAuth } from './react/hooks/useAuth';
6
+ export { logoutKeycloak, refreshTokenKeycloak } from './core/client';
7
+ export type { KeycloakConfig, KeycloakAuthOptions, AuthStatus, AuthUser, AuthContextValue, } from './core/types';
@@ -0,0 +1,5 @@
1
+ import { logoutKeycloak as e, refreshTokenKeycloak as t } from "./core/client.js";
2
+ import { KeycloakProvider as n } from "./react/KeycloakProvider/index.js";
3
+ import { useAuth as r } from "./react/hooks/useAuth.js";
4
+ import { t as i } from "../ProtectedRoute-Bl0cCdQ2.js";
5
+ export { n as KeycloakProvider, i as ProtectedRoute, e as logoutKeycloak, t as refreshTokenKeycloak, r as useAuth };
@@ -0,0 +1,37 @@
1
+ import { ReactNode } from 'react';
2
+ import { AuthContextValue, KeycloakConfig, KeycloakAuthOptions } from '../../core/types';
3
+ /**
4
+ * Contexte React pour l'authentification Keycloak.
5
+ * Utilisé en interne par `useAuth` et `ProtectedRoute`.
6
+ */
7
+ export declare const KeycloakAuthContext: import('react').Context<AuthContextValue | null>;
8
+ /**
9
+ * Props du composant `KeycloakProvider`.
10
+ */
11
+ export interface KeycloakProviderProps extends KeycloakAuthOptions {
12
+ /** Configuration du serveur Keycloak */
13
+ config: KeycloakConfig;
14
+ /** Contenu de l'application */
15
+ children: ReactNode;
16
+ }
17
+ /**
18
+ * Provider d'authentification Keycloak.
19
+ *
20
+ * Initialise la connexion Keycloak, gère le cycle de vie des tokens
21
+ * (rafraîchissement automatique par intervalle et au retour de l'onglet),
22
+ * et expose l'état d'authentification via le contexte React.
23
+ *
24
+ * Basé sur `keycloak-js`. Utilise `check-sso` avec PKCE S256.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * <KeycloakProvider
29
+ * config={{ url: 'https://keycloak.example.com', realm: 'my-realm', clientId: 'my-client' }}
30
+ * idpHint="oidc"
31
+ * refreshInterval={300}
32
+ * >
33
+ * <App />
34
+ * </KeycloakProvider>
35
+ * ```
36
+ */
37
+ export declare function KeycloakProvider({ config, children, idpHint, refreshInterval, minTokenValidity, onAuthError, }: KeycloakProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,70 @@
1
+ import { createKeycloakInstance as e, initKeycloak as t, parseUser as n } from "../../core/client.js";
2
+ import { createContext as r, useCallback as i, useEffect as a, useRef as o, useState as s } from "react";
3
+ import { jsx as c } from "react/jsx-runtime";
4
+ //#region lib/keycloak/react/KeycloakProvider/index.tsx
5
+ var l = r(null);
6
+ function u({ config: r, children: u, idpHint: d, refreshInterval: f = 300, minTokenValidity: p = 30, onAuthError: m }) {
7
+ let [h, g] = s("loading"), [_, v] = s(null), [y, b] = s(null), x = o(null), S = o(!1), C = i((e) => {
8
+ e.authenticated && e.token ? (v(e.token), b(n(e)), g("authenticated")) : (v(null), b(null), g("unauthenticated"));
9
+ }, []), w = i(async (e) => {
10
+ m === "login" ? await x.current?.login({ idpHint: d }) : m === "logout" ? await x.current?.logout({ redirectUri: window.location.origin }) : typeof m == "function" && m(e);
11
+ }, [m, d]), T = i(async () => {
12
+ let e = x.current;
13
+ if (!e) return !1;
14
+ try {
15
+ return await e.updateToken(p) && C(e), !0;
16
+ } catch (e) {
17
+ return console.warn("[dsi-keycloak] Token refresh failed:", e), await w(e), !1;
18
+ }
19
+ }, [
20
+ p,
21
+ C,
22
+ w
23
+ ]);
24
+ a(() => {
25
+ if (S.current) return;
26
+ S.current = !0;
27
+ let n = e(r);
28
+ x.current = n, t(n).then((e) => {
29
+ C(n), e || g("unauthenticated");
30
+ }).catch((e) => {
31
+ console.error("[dsi-keycloak] Init error:", e), g("error");
32
+ });
33
+ }, []), a(() => {
34
+ if (h !== "authenticated") return;
35
+ let e = window.setInterval(() => {
36
+ T();
37
+ }, f * 1e3), t = () => {
38
+ document.visibilityState === "visible" && T();
39
+ };
40
+ return document.addEventListener("visibilitychange", t), () => {
41
+ clearInterval(e), document.removeEventListener("visibilitychange", t);
42
+ };
43
+ }, [
44
+ h,
45
+ f,
46
+ T
47
+ ]);
48
+ let E = i(async (e) => {
49
+ let t = x.current;
50
+ t && await t.login({ idpHint: e ?? d });
51
+ }, [d]), D = i(async (e) => {
52
+ let t = x.current;
53
+ t && await t.logout({ redirectUri: e ?? window.location.origin });
54
+ }, []), O = {
55
+ status: h,
56
+ token: _,
57
+ user: y,
58
+ isAuthenticated: h === "authenticated",
59
+ keycloak: x.current,
60
+ login: E,
61
+ logout: D,
62
+ refreshToken: T
63
+ };
64
+ return /* @__PURE__ */ c(l.Provider, {
65
+ value: O,
66
+ children: u
67
+ });
68
+ }
69
+ //#endregion
70
+ export { l as KeycloakAuthContext, u as KeycloakProvider };
@@ -0,0 +1,69 @@
1
+ import { ReactNode } from 'react';
2
+ import { KeycloakConfig, KeycloakAuthOptions } from '../../core/types';
3
+ /**
4
+ * Props de base du composant `ProtectedRoute`.
5
+ */
6
+ interface BaseProtectedRouteProps {
7
+ /** Contenu protégé, affiché une fois authentifié */
8
+ children: ReactNode;
9
+ /** Composant affiché pendant le chargement / la redirection (défaut: spinner) */
10
+ fallback?: ReactNode;
11
+ /** Composant affiché en cas d'erreur d'authentification (défaut: message d'erreur) */
12
+ errorFallback?: ReactNode;
13
+ }
14
+ /**
15
+ * Props pour le mode standalone (sans `KeycloakProvider` parent).
16
+ * Inclut la configuration Keycloak et les options d'authentification.
17
+ */
18
+ interface StandaloneProtectedRouteProps extends BaseProtectedRouteProps, KeycloakAuthOptions {
19
+ /** Configuration du serveur Keycloak */
20
+ config: KeycloakConfig;
21
+ }
22
+ /**
23
+ * Props pour le mode contexte (à l'intérieur d'un `KeycloakProvider`).
24
+ */
25
+ type ContextProtectedRouteProps = BaseProtectedRouteProps;
26
+ /**
27
+ * Union des props acceptées par `ProtectedRoute`.
28
+ */
29
+ export type ProtectedRouteProps = StandaloneProtectedRouteProps | ContextProtectedRouteProps;
30
+ /**
31
+ * Composant de protection de route / zone authentifiée.
32
+ *
33
+ * Fonctionne en deux modes :
34
+ *
35
+ * **Mode standalone** (plug & play) : fournir `config` et les options directement.
36
+ * Crée automatiquement un `KeycloakProvider` interne.
37
+ *
38
+ * **Mode contexte** : utiliser à l'intérieur d'un `KeycloakProvider` parent.
39
+ * Ne nécessite que `children` et optionnellement `fallback` / `errorFallback`.
40
+ *
41
+ * Dans les deux cas, le composant :
42
+ * - Vérifie l'état d'authentification via `check-sso`
43
+ * - Redirige vers le login Keycloak (via l'IDP configuré) si non authentifié
44
+ * - Affiche un fallback pendant le chargement
45
+ * - Rend les enfants une fois authentifié
46
+ *
47
+ * Basé sur `keycloak-js`.
48
+ *
49
+ * @example Mode standalone (plug & play)
50
+ * ```tsx
51
+ * <ProtectedRoute
52
+ * config={{ url: 'https://keycloak.example.com', realm: 'my-realm', clientId: 'my-client' }}
53
+ * idpHint="oidc"
54
+ * >
55
+ * <Dashboard />
56
+ * </ProtectedRoute>
57
+ * ```
58
+ *
59
+ * @example Mode contexte (avec KeycloakProvider parent)
60
+ * ```tsx
61
+ * <KeycloakProvider config={...} idpHint="oidc">
62
+ * <ProtectedRoute>
63
+ * <Dashboard />
64
+ * </ProtectedRoute>
65
+ * </KeycloakProvider>
66
+ * ```
67
+ */
68
+ export declare function ProtectedRoute(props: ProtectedRouteProps): import("react/jsx-runtime").JSX.Element;
69
+ export {};
@@ -0,0 +1,2 @@
1
+ import { t as e } from "../../../ProtectedRoute-Bl0cCdQ2.js";
2
+ export { e as ProtectedRoute };
@@ -0,0 +1,39 @@
1
+ import { AuthContextValue } from '../../core/types';
2
+ /**
3
+ * Hook d'authentification Keycloak.
4
+ *
5
+ * Donne accès à l'état d'authentification, au token, aux infos utilisateur,
6
+ * et aux actions (login, logout, refreshToken).
7
+ *
8
+ * Doit être utilisé à l'intérieur d'un `KeycloakProvider` ou d'un `ProtectedRoute` avec `config`.
9
+ *
10
+ * @returns Objet d'authentification complet
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * function UserProfile() {
15
+ * const { user, token, isAuthenticated, logout } = useAuth();
16
+ *
17
+ * if (!isAuthenticated) return null;
18
+ *
19
+ * return (
20
+ * <div>
21
+ * <p>Bonjour {user?.name}</p>
22
+ * <button onClick={() => logout()}>Déconnexion</button>
23
+ * </div>
24
+ * );
25
+ * }
26
+ * ```
27
+ *
28
+ * @example Utiliser le token pour des appels API
29
+ * ```tsx
30
+ * function useFetchWithAuth(url: string) {
31
+ * const { token } = useAuth();
32
+ *
33
+ * return fetch(url, {
34
+ * headers: { Authorization: `Bearer ${token}` },
35
+ * });
36
+ * }
37
+ * ```
38
+ */
39
+ export declare function useAuth(): AuthContextValue;
@@ -0,0 +1,10 @@
1
+ import { KeycloakAuthContext as e } from "../KeycloakProvider/index.js";
2
+ import { useContext as t } from "react";
3
+ //#region lib/keycloak/react/hooks/useAuth.ts
4
+ function n() {
5
+ let n = t(e);
6
+ if (!n) throw Error("[dsi-keycloak] useAuth() must be used within a <KeycloakProvider> or a <ProtectedRoute config={...}>.");
7
+ return n;
8
+ }
9
+ //#endregion
10
+ export { n as useAuth };
package/dist/main.d.ts CHANGED
@@ -1 +1,2 @@
1
- export { Example } from './components/Example';
1
+ export { KeycloakProvider, ProtectedRoute, useAuth, logoutKeycloak, refreshTokenKeycloak, } from './keycloak';
2
+ export type { KeycloakProviderProps, ProtectedRouteProps, KeycloakConfig, KeycloakAuthOptions, AuthStatus, AuthUser, AuthContextValue, } from './keycloak';
package/dist/main.js CHANGED
@@ -1,2 +1,6 @@
1
- import { t as e } from "./Example-CYi9vFcY.js";
2
- export { e as Example };
1
+ import { logoutKeycloak as e, refreshTokenKeycloak as t } from "./keycloak/core/client.js";
2
+ import { KeycloakProvider as n } from "./keycloak/react/KeycloakProvider/index.js";
3
+ import { useAuth as r } from "./keycloak/react/hooks/useAuth.js";
4
+ import { t as i } from "./ProtectedRoute-Bl0cCdQ2.js";
5
+ import "./keycloak/index.js";
6
+ export { n as KeycloakProvider, i as ProtectedRoute, e as logoutKeycloak, t as refreshTokenKeycloak, r as useAuth };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shane_donnelly/dsi-internal-react-utils",
3
3
  "private": false,
4
- "version": "0.0.2",
4
+ "version": "0.1.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -28,7 +28,8 @@
28
28
  "vite-plugin-dts": "^4.5.4",
29
29
  "vite-plugin-lib-inject-css": "^2.2.2",
30
30
  "react": "^19.2.4",
31
- "react-dom": "^19.2.4"
31
+ "react-dom": "^19.2.4",
32
+ "keycloak-js": "^26.0.0"
32
33
  },
33
34
  "main": "dist/main.js",
34
35
  "types": "dist/main.d.ts",
@@ -40,6 +41,7 @@
40
41
  ],
41
42
  "peerDependencies": {
42
43
  "react": "^19.2.4",
43
- "react-dom": "^19.2.4"
44
+ "react-dom": "^19.2.4",
45
+ "keycloak-js": ">=25.0.0"
44
46
  }
45
47
  }
@@ -1,12 +0,0 @@
1
- import { jsx as e } from "react/jsx-runtime";
2
- import './assets/Example.css';var t = { text: "_text_acpj5_1" };
3
- //#endregion
4
- //#region lib/components/Example/index.tsx
5
- function n() {
6
- return /* @__PURE__ */ e("p", {
7
- className: t.text,
8
- children: "Example"
9
- });
10
- }
11
- //#endregion
12
- export { n as t };
@@ -1 +0,0 @@
1
- ._text_acpj5_1{color:red}
@@ -1 +0,0 @@
1
- export declare function Example(): import("react/jsx-runtime").JSX.Element;
@@ -1,2 +0,0 @@
1
- import { t as e } from "../../Example-CYi9vFcY.js";
2
- export { e as Example };