diff --git a/.project/concepts/tech-stack/knowledge_build-pipeline.md b/.project/concepts/tech-stack/knowledge_build-pipeline.md index 0d7f20f..a5f56cb 100644 --- a/.project/concepts/tech-stack/knowledge_build-pipeline.md +++ b/.project/concepts/tech-stack/knowledge_build-pipeline.md @@ -14,7 +14,13 @@ Le serveur sert `src/index.html`, qui charge `src/app/frontend.tsx` (voir `app-a ## Détails de `build.ts` et du serveur - `build.ts` scanne `src/**/*.html` comme entrypoints (aujourd'hui un seul : `src/index.html`), `target: 'browser'`, minify + sourcemap linked, plugin `bun-plugin-tailwind`. Ajouter un 2e `.html` créerait un 2e bundle. -- `src/index.ts` (`Bun.serve`) sert : `/reports/cucumber` (rapport HTML), des stubs `/api/hello*`, et un **catch-all `/*` → `src/index.html`** (routing SPA, doit rester en dernier). HMR si `NODE_ENV !== 'production'`, port via `PORT`. +- `src/index.ts` (`Bun.serve`) sert : `/reports/cucumber` (rapport HTML), des stubs `/api/hello*`, `/festipod-config.json` + `/shared-wallet.ngw` (config runtime, voir ci-dessous), et un **catch-all `/*` → `src/index.html`** (routing SPA, doit rester en dernier). HMR si `NODE_ENV !== 'production'`, port via `PORT`. + +## Globals de build vs config runtime (piège du wallet partagé) + +`build.ts` injecte des **globals à la compilation** via `define` (p. ex. `__FESTIPOD_SHARED_WALLET_PASSWORD__` depuis `FESTIPOD_SHARED_WALLET_PASSWORD`, `__FESTIPOD_ACCESS_GATE_DISABLED__`). **Piège** : le serveur `src/index.ts` (utilisé par `bun run dev` ET `bun run start`) bundle `index.html` via l'import HTML de Bun, qui **n'applique aucun `define`** — ni `bun --define` ni `process.env` ne s'y propagent (vérifié). Donc une variable d'env passée à `bun run dev` n'atteint pas le bundle frontend par ce chemin. + +Pour ces chemins servis depuis `src/`, la config passe donc au **runtime** : `src/index.ts` expose `/festipod-config.json` (lu depuis l'env), et l'entrée `src/app/frontend.tsx` la **fetch d'abord**, pose le global, **puis importe l'app dynamiquement** (`await import('./App')`) — ainsi `sharedWallet.ts` lit la valeur à son évaluation. Dans un bundle `build.ts` la valeur est déjà inline par `define`, donc le fetch est court-circuité (`NODE_ENV === 'production'`). Conséquence pratique : pour voir le flux « portefeuille partagé » en dev, lancer `FESTIPOD_SHARED_WALLET_PASSWORD=1 bun run dev` (+ `FESTIPOD_SHARED_WALLET_FILE=<.ngw>` pour un vrai téléchargement). ## Le harness de test est buildé à part diff --git a/src/app/frontend.tsx b/src/app/frontend.tsx index 446e60e..29a466e 100644 --- a/src/app/frontend.tsx +++ b/src/app/frontend.tsx @@ -3,24 +3,54 @@ * element and renders the App component to the DOM. * * It is included in `src/index.html`. + * + * Before loading the app tree it pulls the RUNTIME shared-wallet config (dev + * server + `bun run start`, which serve from src/ and so miss build.ts's + * compile-time `define`), sets the global, then dynamically imports `App` so + * `sharedWallet.ts` reads the value on evaluation. In a build.ts bundle the + * password is already inlined via `define`, so this step is skipped (NODE_ENV). */ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { App } from "./App"; -const elem = document.getElementById("root")!; -const app = ( - - - -); - -if (import.meta.hot) { - // With hot module reloading, `import.meta.hot.data` is persisted. - const root = (import.meta.hot.data.root ??= createRoot(elem)); - root.render(app); -} else { - // The hot module reloading API is not available in production. - createRoot(elem).render(app); +/** Fetch the runtime shared-wallet config and set the global (dev/start only). */ +async function loadRuntimeConfig(): Promise { + if (process.env.NODE_ENV === "production") return; // build.ts define provides it + try { + const res = await fetch("/festipod-config.json"); + if (!res.ok) return; + const cfg = (await res.json()) as { sharedWalletPassword?: string }; + // Bracket access so build.ts's `define` (which matches the dotted global) + // never rewrites this assignment. Only set when the env actually carries one. + const g = globalThis as Record; + if (cfg.sharedWalletPassword && g["__FESTIPOD_SHARED_WALLET_PASSWORD__"] == null) { + g["__FESTIPOD_SHARED_WALLET_PASSWORD__"] = cfg.sharedWalletPassword; + } + } catch { + // No runtime config endpoint (static build) → rely on the compile-time define. + } } + +async function main(): Promise { + await loadRuntimeConfig(); + // Dynamic import AFTER the global is set, so sharedWallet.ts reads it on eval. + const { App } = await import("./App"); + const elem = document.getElementById("root")!; + const app = ( + + + + ); + + if (import.meta.hot) { + // With hot module reloading, `import.meta.hot.data` is persisted. + const root = (import.meta.hot.data.root ??= createRoot(elem)); + root.render(app); + } else { + // The hot module reloading API is not available in production. + createRoot(elem).render(app); + } +} + +void main(); diff --git a/src/index.ts b/src/index.ts index 7351a3f..e58c516 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,25 @@ const server = serve({ }); }, + // Shared-wallet config, exposed at RUNTIME for the dev server + `bun run start` + // (both serve from src/, so they miss build.ts's compile-time `define`). The app + // entry (frontend.tsx) fetches this before it loads the app tree, so + // `sharedWallet.ts` sees the password. Empty env → '' → no shared wallet. + "/festipod-config.json": () => + Response.json({ + sharedWalletPassword: process.env.FESTIPOD_SHARED_WALLET_PASSWORD ?? "", + }), + + // The shared wallet file (download target of the access barrier), when configured. + "/shared-wallet.ngw": async () => { + const p = process.env.FESTIPOD_SHARED_WALLET_FILE; + if (p) { + const file = Bun.file(p); + if (await file.exists()) return new Response(file); + } + return new Response("No shared wallet file configured.", { status: 404 }); + }, + // Serve index.html for all unmatched routes (must be last) "/*": index, }, diff --git a/src/modules/auth/screens/AccessGateScreen.tsx b/src/modules/auth/screens/AccessGateScreen.tsx index d35789e..ddfce2f 100644 --- a/src/modules/auth/screens/AccessGateScreen.tsx +++ b/src/modules/auth/screens/AccessGateScreen.tsx @@ -99,7 +99,7 @@ export function AccessGateScreen({ status, error, onEnter }: AccessGateScreenPro Festipod Espace de test - {hasSharedWallet() ? ( + {hasSharedWallet() && status !== 'connected' ? ( <> Première connexion sur cet appareil ?
Chargez le portefeuille partagé, une seule fois.