@shane_donnelly/dsi-internal-react-utils 0.1.3 → 0.1.4

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
@@ -26,7 +26,7 @@ import '@shane_donnelly/dsi-internal-react-utils/style.css'
26
26
 
27
27
  ### Keycloak
28
28
 
29
- Wrapper de simplification pour `keycloak-js`. Gère l'authentification automatique via Identity Provider, le refresh de token, et la protection de routes/zones.
29
+ Wrapper de simplification pour `keycloak-js`. `KeycloakProvider` singleton à la racine, `useKeycloakAuth` pour accéder à l'état d'auth et déclencher login / logout depuis n'importe quel composant.
30
30
 
31
31
  [Documentation complète du module Keycloak](docs/keycloak.md)
32
32
 
@@ -39,6 +39,7 @@ Wrapper de simplification pour `keycloak-js`. Gère l'authentification automatiq
39
39
  import React from 'react';
40
40
  import ReactDOM from 'react-dom/client';
41
41
  import { BrowserRouter } from 'react-router-dom';
42
+ import '@shane_donnelly/dsi-internal-react-utils/style.css';
42
43
  import App from './App';
43
44
 
44
45
  ReactDOM.createRoot(document.getElementById('root')!).render(
@@ -53,10 +54,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
53
54
  ```tsx
54
55
  // App.tsx
55
56
  import { Routes, Route } from 'react-router-dom';
56
- import { KeycloakProvider, ProtectedRoute } from '@shane_donnelly/dsi-internal-react-utils';
57
+ import { KeycloakProvider } from '@shane_donnelly/dsi-internal-react-utils';
57
58
  import PublicPage from './pages/PublicPage';
58
59
  import Dashboard from './pages/Dashboard';
59
- import Profile from './pages/Profile';
60
60
 
61
61
  const keycloakConfig = {
62
62
  url: 'https://keycloak.example.com',
@@ -68,26 +68,8 @@ export default function App() {
68
68
  return (
69
69
  <KeycloakProvider config={keycloakConfig} idpHint="oidc" refreshInterval={300}>
70
70
  <Routes>
71
- {/* Route publique — accessible sans authentification */}
72
71
  <Route path="/" element={<PublicPage />} />
73
-
74
- {/* Routes protégées — redirection automatique vers l'IDP */}
75
- <Route
76
- path="/dashboard"
77
- element={
78
- <ProtectedRoute>
79
- <Dashboard />
80
- </ProtectedRoute>
81
- }
82
- />
83
- <Route
84
- path="/profile"
85
- element={
86
- <ProtectedRoute fallback={<p>Chargement du profil...</p>}>
87
- <Profile />
88
- </ProtectedRoute>
89
- }
90
- />
72
+ <Route path="/dashboard" element={<Dashboard />} />
91
73
  </Routes>
92
74
  </KeycloakProvider>
93
75
  );
@@ -96,51 +78,43 @@ export default function App() {
96
78
 
97
79
  ```tsx
98
80
  // pages/Dashboard.tsx
99
- import { useAuth } from '@shane_donnelly/dsi-internal-react-utils';
81
+ import { useEffect } from 'react';
82
+ import { useNavigate } from 'react-router-dom';
83
+ import { useKeycloakAuth } from '@shane_donnelly/dsi-internal-react-utils';
100
84
 
101
85
  export default function Dashboard() {
102
- const { user, token, logout } = useAuth();
86
+ const { isAuthenticated, status, user, login, logout } = useKeycloakAuth();
87
+ const navigate = useNavigate();
88
+
89
+ useEffect(() => {
90
+ if (status === 'unauthenticated') {
91
+ login({ idpHint: 'oidc' });
92
+ }
93
+ }, [status, login]);
94
+
95
+ if (status === 'loading' || status === 'unauthenticated') return <p>Chargement...</p>;
96
+ if (status === 'error') return <p>Erreur d'authentification.</p>;
103
97
 
104
98
  return (
105
99
  <div>
106
100
  <h1>Bienvenue {user?.name}</h1>
107
101
  <p>Email : {user?.email}</p>
108
- <button onClick={() => logout()}>Se déconnecter</button>
102
+ {/* Déconnexion avec redirection KC vers l'accueil */}
103
+ <button onClick={() => logout({ redirectUri: window.location.origin })}>
104
+ Se déconnecter
105
+ </button>
109
106
  </div>
110
107
  );
111
108
  }
112
109
  ```
113
110
 
114
- ### Mode plug & play (sans Provider)
115
-
116
- Pour un usage rapide sans `KeycloakProvider`, passez `config` directement à `ProtectedRoute` :
117
-
118
- ```tsx
119
- import { ProtectedRoute } from '@shane_donnelly/dsi-internal-react-utils';
120
-
121
- function App() {
122
- return (
123
- <ProtectedRoute
124
- config={{
125
- url: 'https://keycloak.example.com',
126
- realm: 'my-realm',
127
- clientId: 'my-frontend',
128
- }}
129
- idpHint="oidc"
130
- >
131
- <Dashboard />
132
- </ProtectedRoute>
133
- );
134
- }
135
- ```
136
-
137
111
  ### Utiliser le token pour les appels API
138
112
 
139
113
  ```tsx
140
- import { useAuth } from '@shane_donnelly/dsi-internal-react-utils';
114
+ import { useKeycloakAuth } from '@shane_donnelly/dsi-internal-react-utils';
141
115
 
142
116
  function useAuthFetch() {
143
- const { token } = useAuth();
117
+ const { token } = useKeycloakAuth();
144
118
 
145
119
  return (url: string, options?: RequestInit) =>
146
120
  fetch(url, {
@@ -156,10 +130,10 @@ function useAuthFetch() {
156
130
  ### Accéder à l'instance keycloak-js (usage avancé)
157
131
 
158
132
  ```tsx
159
- import { useAuth } from '@shane_donnelly/dsi-internal-react-utils';
133
+ import { useKeycloakAuth } from '@shane_donnelly/dsi-internal-react-utils';
160
134
 
161
135
  function AdvancedComponent() {
162
- const { keycloak } = useAuth();
136
+ const { keycloak } = useKeycloakAuth();
163
137
 
164
138
  // Accès direct à l'instance keycloak-js pour des cas spécifiques
165
139
  console.log(keycloak?.tokenParsed);
@@ -171,10 +145,10 @@ function AdvancedComponent() {
171
145
  Deux fonctions sont disponibles pour manipuler l'instance Keycloak en dehors de React (intercepteurs HTTP, services, etc.) :
172
146
 
173
147
  ```tsx
174
- import { useAuth, logoutKeycloak, refreshTokenKeycloak } from '@shane_donnelly/dsi-internal-react-utils';
148
+ import { useKeycloakAuth, logoutKeycloak, refreshTokenKeycloak } from '@shane_donnelly/dsi-internal-react-utils';
175
149
 
176
- // Récupérer l'instance keycloak via useAuth()
177
- const { keycloak } = useAuth();
150
+ // Récupérer l'instance keycloak via useKeycloakAuth()
151
+ const { keycloak } = useKeycloakAuth();
178
152
 
179
153
  // Rafraîchir le token (minValidity en secondes, défaut: 30)
180
154
  const refreshed = await refreshTokenKeycloak(keycloak!, 60);
@@ -20,9 +20,25 @@ export type KeycloakAuthOptions = {
20
20
  refreshInterval?: number;
21
21
  /** Validité minimale du token en secondes avant rafraîchissement (défaut: 30) */
22
22
  minTokenValidity?: number;
23
- /** Comportement en cas d'erreur d'auth: 'login' pour rediriger, 'logout' pour déconnecter, ou un handler custom */
23
+ /** Comportement en cas d'erreur de refresh token: 'login' pour rediriger, 'logout' pour déconnecter, ou un handler custom */
24
24
  onAuthError?: 'login' | 'logout' | ((error: unknown) => void);
25
25
  };
26
+ /**
27
+ * Options pour déclencher le login Keycloak.
28
+ */
29
+ export type LoginOptions = {
30
+ /** Identity provider hint, override le `idpHint` du `KeycloakProvider` */
31
+ idpHint?: string;
32
+ /** URI vers laquelle Keycloak redirige après le login */
33
+ redirectUri?: string;
34
+ };
35
+ /**
36
+ * Options pour déclencher le logout.
37
+ */
38
+ export type LogoutOptions = {
39
+ /** URI de redirection après déconnexion (défaut: `window.location.origin`) */
40
+ redirectUri?: string;
41
+ };
26
42
  /**
27
43
  * État de l'authentification.
28
44
  * - `loading` : vérification en cours
@@ -45,7 +61,7 @@ export type AuthUser = {
45
61
  [key: string]: unknown;
46
62
  };
47
63
  /**
48
- * Valeur du contexte d'authentification exposée par `KeycloakProvider` et `useAuth`.
64
+ * Valeur du contexte d'authentification exposée par `KeycloakProvider` et `useKeycloakAuth`.
49
65
  */
50
66
  export type AuthContextValue = {
51
67
  /** État actuel de l'authentification */
@@ -58,10 +74,10 @@ export type AuthContextValue = {
58
74
  isAuthenticated: boolean;
59
75
  /** Instance keycloak-js sous-jacente (pour usage avancé) */
60
76
  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>;
77
+ /** Déclencher une redirection vers le login Keycloak */
78
+ login: (options?: LoginOptions) => Promise<void>;
79
+ /** Déclencher le logout. Redirige vers `redirectUri` après déconnexion (défaut: `window.location.origin`). */
80
+ logout: (options?: LogoutOptions) => Promise<void>;
65
81
  /** Rafraîchir manuellement le token. Retourne true si réussi. */
66
82
  refreshToken: () => Promise<boolean>;
67
83
  };
@@ -1,7 +1,5 @@
1
1
  export { KeycloakProvider } from './react/KeycloakProvider';
2
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';
3
+ export { useKeycloakAuth } from './react/hooks/useKeycloakAuth';
6
4
  export { logoutKeycloak, refreshTokenKeycloak } from './core/client';
7
- export type { KeycloakConfig, KeycloakAuthOptions, AuthStatus, AuthUser, AuthContextValue, } from './core/types';
5
+ export type { KeycloakConfig, KeycloakAuthOptions, AuthStatus, AuthUser, AuthContextValue, LoginOptions, LogoutOptions, } from './core/types';
@@ -1,5 +1,4 @@
1
1
  import { logoutKeycloak as e, refreshTokenKeycloak as t } from "./core/client.js";
2
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-_48urWWd.js";
5
- export { n as KeycloakProvider, i as ProtectedRoute, e as logoutKeycloak, t as refreshTokenKeycloak, r as useAuth };
3
+ import { useKeycloakAuth as r } from "./react/hooks/useKeycloakAuth.js";
4
+ export { n as KeycloakProvider, e as logoutKeycloak, t as refreshTokenKeycloak, r as useKeycloakAuth };
@@ -47,10 +47,13 @@ function u({ config: r, children: u, idpHint: d, refreshInterval: f = 300, minTo
47
47
  ]);
48
48
  let E = i(async (e) => {
49
49
  let t = x.current;
50
- t && await t.login({ idpHint: e ?? d });
50
+ t && await t.login({
51
+ idpHint: e?.idpHint ?? d,
52
+ redirectUri: e?.redirectUri
53
+ });
51
54
  }, [d]), D = i(async (e) => {
52
55
  let t = x.current;
53
- t && await t.logout({ redirectUri: e ?? window.location.origin });
56
+ t && await t.logout({ redirectUri: e?.redirectUri ?? window.location.origin });
54
57
  }, []), O = {
55
58
  status: h,
56
59
  token: _,
@@ -5,14 +5,14 @@ import { AuthContextValue } from '../../core/types';
5
5
  * Donne accès à l'état d'authentification, au token, aux infos utilisateur,
6
6
  * et aux actions (login, logout, refreshToken).
7
7
  *
8
- * Doit être utilisé à l'intérieur d'un `KeycloakProvider` ou d'un `ProtectedRoute` avec `config`.
8
+ * Doit être utilisé à l'intérieur d'un `KeycloakProvider`.
9
9
  *
10
10
  * @returns Objet d'authentification complet
11
11
  *
12
12
  * @example
13
13
  * ```tsx
14
14
  * function UserProfile() {
15
- * const { user, token, isAuthenticated, logout } = useAuth();
15
+ * const { user, token, isAuthenticated, logout } = useKeycloakAuth();
16
16
  *
17
17
  * if (!isAuthenticated) return null;
18
18
  *
@@ -25,10 +25,24 @@ import { AuthContextValue } from '../../core/types';
25
25
  * }
26
26
  * ```
27
27
  *
28
+ * @example Déconnexion avec navigation React Router
29
+ * ```tsx
30
+ * function LogoutButton() {
31
+ * const { logout } = useKeycloakAuth();
32
+ * const navigate = useNavigate();
33
+ *
34
+ * return (
35
+ * <button onClick={() => logout({ onSuccess: () => navigate('/') })}>
36
+ * Se déconnecter
37
+ * </button>
38
+ * );
39
+ * }
40
+ * ```
41
+ *
28
42
  * @example Utiliser le token pour des appels API
29
43
  * ```tsx
30
44
  * function useFetchWithAuth(url: string) {
31
- * const { token } = useAuth();
45
+ * const { token } = useKeycloakAuth();
32
46
  *
33
47
  * return fetch(url, {
34
48
  * headers: { Authorization: `Bearer ${token}` },
@@ -36,4 +50,4 @@ import { AuthContextValue } from '../../core/types';
36
50
  * }
37
51
  * ```
38
52
  */
39
- export declare function useAuth(): AuthContextValue;
53
+ export declare function useKeycloakAuth(): 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/useKeycloakAuth.ts
4
+ function n() {
5
+ let n = t(e);
6
+ if (!n) throw Error("[dsi-keycloak] useKeycloakAuth() must be used within a <KeycloakProvider>.");
7
+ return n;
8
+ }
9
+ //#endregion
10
+ export { n as useKeycloakAuth };
package/dist/main.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { KeycloakProvider, ProtectedRoute, useAuth, logoutKeycloak, refreshTokenKeycloak, } from './keycloak';
2
- export type { KeycloakProviderProps, ProtectedRouteProps, KeycloakConfig, KeycloakAuthOptions, AuthStatus, AuthUser, AuthContextValue, } from './keycloak';
1
+ export { KeycloakProvider, useKeycloakAuth, logoutKeycloak, refreshTokenKeycloak, } from './keycloak';
2
+ export type { KeycloakProviderProps, KeycloakConfig, KeycloakAuthOptions, AuthStatus, AuthUser, AuthContextValue, LoginOptions, LogoutOptions, } from './keycloak';
package/dist/main.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { logoutKeycloak as e, refreshTokenKeycloak as t } from "./keycloak/core/client.js";
2
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-_48urWWd.js";
3
+ import { useKeycloakAuth as r } from "./keycloak/react/hooks/useKeycloakAuth.js";
5
4
  import "./keycloak/index.js";
6
- export { n as KeycloakProvider, i as ProtectedRoute, e as logoutKeycloak, t as refreshTokenKeycloak, r as useAuth };
5
+ export { n as KeycloakProvider, e as logoutKeycloak, t as refreshTokenKeycloak, r as useKeycloakAuth };
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.1.3",
4
+ "version": "0.1.4",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -1,55 +0,0 @@
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
- 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 };
@@ -1,69 +0,0 @@
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 {};
@@ -1,2 +0,0 @@
1
- import { t as e } from "../../../ProtectedRoute-_48urWWd.js";
2
- export { e as ProtectedRoute };
@@ -1,10 +0,0 @@
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/style.css DELETED
@@ -1,2 +0,0 @@
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}
2
- /*$vite$:1*/