fix: régler l'identité ne demande pas de session, se connecter oui

init() de @ng-org/web redirige vers le broker en première instruction, dès qu'on
est en tête. L'application appelait donc init() au chargement du module, la page
partait, et ensureIdentity() ne s'exécutait jamais : la barrière n'apparaissait
pas, ?ng-id= restait absent de l'URL remise au broker, et un primo-arrivant se
retrouvait devant la page de connexion sans portefeuille et sans moyen d'en
obtenir un — sans la moindre erreur.

Appeler ensureIdentity() avant init() ne marchait pas non plus : il attend la
session, que seul le callback d'init() résout. Cycle vérifié empiriquement.

La cause n'était ni l'ordre ni la redirection, mais une confusion dans
ensureIdentity() entre deux actes de nature différente — régler qui est
l'utilisateur (barrière, URL, stockage : aucune session) et se connecter
(session requise). settleIdentity() porte le premier ; le wrapper init() du
polyfill l'attend avant de déléguer. L'invariant d'ordre est ainsi porté par la
composition, pas par une consigne d'ordre d'appel que personne ne lit.

Piège trouvé et épinglé en écrivant les tests : init() et ensureIdentity() dans
le même tick montaient deux barrières, l'utilisateur répondait à l'une et
l'autre ne se résolvait jamais. Le règlement en vol est désormais partagé.
This commit is contained in:
Sylvain Duchesne
2026-08-11 12:52:49 +02:00
parent fc3c129bd3
commit 3547de202c
5 changed files with 392 additions and 39 deletions
+35 -4
View File
@@ -1,17 +1,48 @@
/**
* Lifecycle re-exports — SDK-shaped forwarders so the app imports `init` /
* `initNg` from `@ng-eventually/polyfill` rather than from `@ng-org/*`. They
* delegate to the REAL functions injected at `configure()`. Passthrough today;
* a hook point later (e.g. opening the shared wallet on `init`).
* delegate to the REAL functions injected at `configure()`.
*
* ── `init` is not a bare passthrough, and that is the point ────────────────
* The real `init()` hands the page to the broker as its FIRST statement — a top-level
* document is navigated to NextGraph's login, carrying `window.location.href` AS IT FINDS
* IT. It knows nothing of `?ng-id=`, and it does not come back: everything the application
* would have run after that line runs in a document that no longer exists. So an
* application that signed in on the next line never signed in at all — the barrier never
* showed, the identifier never reached the URL that crossed, and a first-time user landed
* on a login with no wallet and no error anywhere.
*
* The obvious remedy — "call the gate first" — is an ordering rule written in prose, which
* every consumer gets to get wrong once; and it deadlocks besides, because signing in used
* to include waiting for the session `init()` is what opens. So the invariant is carried
* HERE, by composition: this forwarder settles the identity, then delegates. A caller
* cannot get the order wrong because a caller no longer takes part in it
* (`shared-wallet/access-gate.ts`, {@link settleIdentity}).
*/
import { getConfig } from "../shared-wallet/bootstrap";
import { settleIdentity } from "../shared-wallet/access-gate";
/** Forwards to the real `@ng-org/web` `init`. */
/**
* Forwards to the real `@ng-org/web` `init`, once the identifier is in the address bar.
*
* Awaits {@link settleIdentity} — the session-free half of signing in — and NOT
* `ensureIdentity()`, which also awaits the connection work, which awaits `getSession()`,
* which resolves only from the session `init()` has not opened yet. That wait is the
* deadlock, and avoiding it is what the split in `access-gate.ts` is for.
*
* A settling failure REJECTS rather than delegating. No shared wallet configured, or no DOM
* to ask on, means there is no identifier to hand over — and handing the page to the broker
* anyway IS the defect, a navigation the user cannot come back from. It fails at the call
* the application made, where the cause is.
*
* The "not injected" error stays SYNCHRONOUS: it is a wiring mistake rather than a runtime
* one, and it threw synchronously before this forwarder had anything to await.
*/
export function init(...args: any[]): any {
const f = getConfig().init;
if (!f) throw new Error("[ng-eventually] init() not injected — pass it to configure()");
return f(...args);
return settleIdentity().then(() => f(...args));
}
/** Forwards to the real `@ng-org/orm` `initNg` (ORM signals). */