@inwink-mcp/backoffice-extension-helpers 1.0.3 → 1.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inwink-mcp/backoffice-extension-helpers",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "author": "inwink",
5
5
  "license": "ISC",
6
6
  "description": "CLI pour bootstrapper, packager et vérifier les extensions backoffice inwink.",
@@ -1,5 +1,6 @@
1
1
  import { Navigate, Route, Routes } from 'react-router-dom';
2
2
  import ProtectedRoute from './auth/ProtectedRoute';
3
+ import Layout from './components/Layout';
3
4
  import HomePage from './pages/HomePage';
4
5
  // Pour appeler une API inwink, utilisez un hook dédié (jamais fetch directement) :
5
6
  // import { useInwinkEventApi } from './hooks/useInwinkEventApi';
@@ -9,14 +10,16 @@ import HomePage from './pages/HomePage';
9
10
  export default function App() {
10
11
  return (
11
12
  <Routes>
12
- <Route
13
- path="/"
14
- element={
15
- <ProtectedRoute>
16
- <HomePage />
17
- </ProtectedRoute>
18
- }
19
- />
13
+ <Route element={<Layout title="__DISPLAY_NAME__" />}>
14
+ <Route
15
+ path="/"
16
+ element={
17
+ <ProtectedRoute>
18
+ <HomePage />
19
+ </ProtectedRoute>
20
+ }
21
+ />
22
+ </Route>
20
23
  <Route path="*" element={<Navigate to="/" replace />} />
21
24
  </Routes>
22
25
  );
@@ -1,3 +1,4 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
1
2
  import { useAuth } from './useAuth';
2
3
 
3
4
  /** Erreur levée lorsqu'une réponse API n'est pas OK (hors 401 géré par renouvellement). */
@@ -36,66 +37,76 @@ async function safeBody(response: Response): Promise<unknown> {
36
37
  * sert aux hôtes externes déclarés ou à des bases d'URL spécifiques.
37
38
  */
38
39
  export function useInwinkApi(baseUrl: string): InwinkApi {
39
- const { getAccessToken, renewToken, login } = useAuth();
40
+ // Les fonctions d'auth sont lues via une ref (motif « latest ref », cf. `userRef`
41
+ // dans AuthProvider) : l'objet API renvoyé reste ainsi stable (mémoïsé sur `baseUrl`,
42
+ // une constante) tout en utilisant toujours les callbacks frais au moment de l'appel.
43
+ const auth = useAuth();
44
+ const authRef = useRef(auth);
45
+ useEffect(() => {
46
+ authRef.current = auth;
47
+ }, [auth]);
40
48
 
41
- async function doFetch(path: string, token: string, init?: RequestInit): Promise<Response> {
42
- return fetch(`${baseUrl}${path}`, {
43
- ...init,
44
- headers: {
45
- Authorization: `Bearer ${token}`,
46
- 'Content-Type': 'application/json',
47
- ...(init?.headers as Record<string, string> | undefined),
48
- },
49
- });
50
- }
51
-
52
- async function request<T = unknown>(path: string, init?: RequestInit): Promise<T> {
53
- let token = getAccessToken();
54
- if (!token) {
55
- await login();
56
- throw new InwinkApiError(401, 'Non authentifié.');
49
+ return useMemo<InwinkApi>(() => {
50
+ async function doFetch(path: string, token: string, init?: RequestInit): Promise<Response> {
51
+ return fetch(`${baseUrl}${path}`, {
52
+ ...init,
53
+ headers: {
54
+ Authorization: `Bearer ${token}`,
55
+ 'Content-Type': 'application/json',
56
+ ...(init?.headers as Record<string, string> | undefined),
57
+ },
58
+ });
57
59
  }
58
60
 
59
- let response = await doFetch(path, token, init);
60
-
61
- if (response.status === 401) {
62
- const renewed = await renewToken();
63
- if (renewed) {
64
- token = renewed;
65
- response = await doFetch(path, token, init);
61
+ async function request<T = unknown>(path: string, init?: RequestInit): Promise<T> {
62
+ const { getAccessToken, renewToken, login } = authRef.current;
63
+ let token = getAccessToken();
64
+ if (!token) {
65
+ await login();
66
+ throw new InwinkApiError(401, 'Non authentifié.');
66
67
  }
68
+
69
+ let response = await doFetch(path, token, init);
70
+
67
71
  if (response.status === 401) {
68
- await login();
69
- throw new InwinkApiError(401, 'Session expirée, reconnexion requise.');
72
+ const renewed = await renewToken();
73
+ if (renewed) {
74
+ token = renewed;
75
+ response = await doFetch(path, token, init);
76
+ }
77
+ if (response.status === 401) {
78
+ await login();
79
+ throw new InwinkApiError(401, 'Session expirée, reconnexion requise.');
80
+ }
70
81
  }
71
- }
72
82
 
73
- if (!response.ok) {
74
- throw new InwinkApiError(response.status, await safeBody(response));
75
- }
83
+ if (!response.ok) {
84
+ throw new InwinkApiError(response.status, await safeBody(response));
85
+ }
76
86
 
77
- if (response.status === 204) {
78
- return undefined as T;
87
+ if (response.status === 204) {
88
+ return undefined as T;
89
+ }
90
+ return (await response.json()) as T;
79
91
  }
80
- return (await response.json()) as T;
81
- }
82
92
 
83
- const withBody =
84
- (method: string) =>
85
- <T = unknown>(path: string, body?: unknown, init?: RequestInit): Promise<T> =>
86
- request<T>(path, {
87
- ...init,
88
- method,
89
- body: body === undefined ? init?.body : JSON.stringify(body),
90
- });
93
+ const withBody =
94
+ (method: string) =>
95
+ <T = unknown>(path: string, body?: unknown, init?: RequestInit): Promise<T> =>
96
+ request<T>(path, {
97
+ ...init,
98
+ method,
99
+ body: body === undefined ? init?.body : JSON.stringify(body),
100
+ });
91
101
 
92
- return {
93
- request,
94
- get: <T = unknown>(path: string, init?: RequestInit) =>
95
- request<T>(path, { ...init, method: 'GET' }),
96
- post: withBody('POST'),
97
- put: withBody('PUT'),
98
- del: <T = unknown>(path: string, init?: RequestInit) =>
99
- request<T>(path, { ...init, method: 'DELETE' }),
100
- };
102
+ return {
103
+ request,
104
+ get: <T = unknown>(path: string, init?: RequestInit) =>
105
+ request<T>(path, { ...init, method: 'GET' }),
106
+ post: withBody('POST'),
107
+ put: withBody('PUT'),
108
+ del: <T = unknown>(path: string, init?: RequestInit) =>
109
+ request<T>(path, { ...init, method: 'DELETE' }),
110
+ };
111
+ }, [baseUrl]);
101
112
  }
@@ -3,13 +3,16 @@ import ReactDOM from 'react-dom/client';
3
3
  import { HashRouter } from 'react-router-dom';
4
4
  import App from './App';
5
5
  import { AuthProvider } from './auth/AuthProvider';
6
+ import { InwinkContextProvider } from './context/InwinkContextProvider';
6
7
  import './styles.css';
7
8
 
8
9
  ReactDOM.createRoot(document.getElementById('root')!).render(
9
10
  <React.StrictMode>
10
11
  <HashRouter>
11
12
  <AuthProvider>
12
- <App />
13
+ <InwinkContextProvider>
14
+ <App />
15
+ </InwinkContextProvider>
13
16
  </AuthProvider>
14
17
  </HashRouter>
15
18
  </React.StrictMode>,