fix: l'identifiant est dans l'URL avant qu'init() ne l'emporte
@ng-org/web fait déjà la redirection vers le broker — même hôte, même forme, même test de cadre (dist/ngweb.js). Le polyfill n'a donc rien à réimplémenter là : ce qui lui revient, c'est la seule chose qu'init() ne peut pas faire, à savoir mettre ?ng-id= dans window.location.href avant qu'il ne le lise. rememberIdentity() s'exécute désormais sur les trois chemins — identité posée par l'appelant, venue du stockage, ou saisie à la barrière. Le cas du stockage était le silencieux : le paramètre restait absent, l'iframe lisait une identité vide et provisionnait un second espace virtuel, sans erreur. L'écriture dans le stockage et celle dans la barre d'adresse sont deux try indépendants : un stockage qui refuse d'écrire ne doit pas emporter avec lui le paramètre, qui est le seul à franchir la frontière de partition. Le contrat retire l'obligation « être ouverte via la redirection du broker » : elle n'a jamais été celle de l'application. Ni broker, ni iframe, ni redirection n'y sont plus nommés.
This commit is contained in:
@@ -37,6 +37,21 @@
|
||||
*
|
||||
* Getting this wrong does not fail loudly: the iframe reads an empty identity, provisions
|
||||
* a second virtual user, and the returning user silently lands in an empty space.
|
||||
*
|
||||
* ── The hand-over to the broker is `init()`'s, and stays there ────────────
|
||||
* The round-trip is a redirect to NextGraph's login, which opens the wallet and loads the
|
||||
* application back INSIDE an iframe it hosts. **`ng.init()` already performs it** — it
|
||||
* compares `window.self` to `window.top` and, when top-level, navigates to its own
|
||||
* redirect URL built from `window.location.href` (`@ng-org/web`, `dist/ngweb.js`). The
|
||||
* level below covers the need, so this module implements NOTHING of it: doubling it would
|
||||
* be code to delete at migration that diverges in the meantime (it did, briefly — a
|
||||
* `sessionStorage` loop guard here made the hand-over not happen where upstream's does).
|
||||
*
|
||||
* What upstream cannot do is the ORDER, because it does not know this parameter exists:
|
||||
* it hands over `window.location.href` AS IT FINDS IT. So the one thing this module owes
|
||||
* the round-trip is that `?ng-id=` is already in the address bar when `init()` reads it —
|
||||
* on every path, whatever settled the identity. Hence {@link rememberIdentity} on all
|
||||
* three, and hence `ensureIdentity()` before `init()` in an application's bootstrap.
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -113,16 +128,35 @@ function storedIdentity(): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
/** Put the identifier where the round-trip can find it, then remember it locally. */
|
||||
/**
|
||||
* Put the identifier where the round-trip can find it, and remember it locally.
|
||||
*
|
||||
* Called on EVERY path that settles an identity — typed at the gate, read from storage,
|
||||
* or already set by the caller — and that is the whole point. `init()` hands the broker
|
||||
* `window.location.href` as it finds it, so a path that settles an identity without
|
||||
* writing the param sends the round-trip off without it: the iframe reads no identity,
|
||||
* provisions a second virtual user, and the returning user lands in an empty space with
|
||||
* no error anywhere. The identity coming from storage is the case that bites, since
|
||||
* storage is precisely what does NOT cross the partition.
|
||||
*
|
||||
* Two independent effects, deliberately not one `try`: the address bar is what crosses
|
||||
* the frontier and storage is only same-partition convenience, so a storage that refuses
|
||||
* writes (private mode, quota) must not cost the round-trip its parameter.
|
||||
*/
|
||||
function rememberIdentity(id: string): void {
|
||||
try {
|
||||
globalThis.localStorage?.setItem(STORAGE_KEY, id);
|
||||
} catch {
|
||||
// Same-partition convenience only: a reload will ask again, the round-trip still works.
|
||||
}
|
||||
try {
|
||||
const url = new URL(globalThis.location!.href);
|
||||
url.searchParams.set(URL_PARAM, id);
|
||||
globalThis.history?.replaceState(null, "", url.toString());
|
||||
} catch {
|
||||
// Nothing to do: without the param the round-trip loses the identity and the gate
|
||||
// will ask again, which is the safe failure.
|
||||
// No location, or a document forbidden to rewrite its URL (sandboxed): the round-trip
|
||||
// loses the identity and the gate asks again, which is the safe failure. Nothing here
|
||||
// can substitute for it — the URL `init()` hands over is the browser's, not ours.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +256,13 @@ function askForIdentity(cfg: SharedWalletConfig): Promise<string> {
|
||||
* A returning user never sees the gate: the identifier survives the broker round-trip in
|
||||
* the URL, and a plain reload finds it in storage.
|
||||
*
|
||||
* **Call it before `init()`.** Whatever settled the identity — typed, stored, or set by
|
||||
* the caller — this leaves `?ng-id=` in the address bar, and `init()` hands the broker the
|
||||
* address bar as it finds it. The other order signs the user in and then sends the
|
||||
* round-trip off without the identifier, which fails silently (see
|
||||
* {@link rememberIdentity}). It cannot be enforced from inside `init()`: this call awaits
|
||||
* the connection work, which awaits the session, which `init()` is what establishes.
|
||||
*
|
||||
* **It RETURNS the identity it settled**, and that is not a convenience — it is the only
|
||||
* way an application can know who it is. Upstream the question does not arise: an app
|
||||
* passes `user_id` to `session_start(wallet_name, user_id)` (`@ng-org/web`), having got it
|
||||
@@ -233,12 +274,16 @@ function askForIdentity(cfg: SharedWalletConfig): Promise<string> {
|
||||
export async function ensureIdentity(): Promise<PrincipalId> {
|
||||
const already = getCurrentUser();
|
||||
if (already !== null) {
|
||||
rememberIdentity(already);
|
||||
await connected();
|
||||
return already;
|
||||
}
|
||||
|
||||
const known = storedIdentity();
|
||||
if (known) {
|
||||
// Even though `storedIdentity()` just read it: what it read may have come from THIS
|
||||
// partition's storage, which the round-trip does not carry. The address bar does.
|
||||
rememberIdentity(known);
|
||||
setCurrentUser(known);
|
||||
await connected();
|
||||
return known;
|
||||
|
||||
Reference in New Issue
Block a user