Files
ng-eventually/packages/ng-e2e-helpers/src/wallet-export-page.ts
T
Sylvain Duchesne b50591f5bd feat(e2e-helpers): fabriquer un portefeuille, pour un test comme pour un déploiement
Trois appelants veulent la même chose — un portefeuille neuf, ses octets et son
mot de passe : la suite du polyfill à son démarrage, la suite d'une application
au sien, et un humain une fois pour provisionner un déploiement. Rien ne
produisait ce fichier ; il fallait aller le chercher à la main sur nextgraph.eu.

Mêmes entrées, mêmes sorties, mais des appelants de nature différente : une
suite ne va pas lancer un sous-processus et analyser sa sortie, et un humain ne
va pas écrire un fichier jetable pour appeler une fonction. Donc une fonction,
et un script mince par-dessus.

mintWalletBytes(password, name) rend les OCTETS, pas un chemin. C'est une
correction de ce qui existait : exportWalletFile imposait le disque à tout le
monde, et la suite applicative écrivait un fichier temporaire pour le relire
aussitôt en mémoire, puis devait le nettoyer. Elle ne le fait plus — serveApp
prend les octets. Qui veut un fichier l'écrit ; personne n'y est forcé.

Le script exige --password et refuse d'en inventer un, et refuse d'écraser un
.ngw existant sans --force, vérifié AVANT de fabriquer quoi que ce soit.
Exécuté pour de vrai : 800 octets, et le mot de passe imprimé est celui passé en
entrée. C'est le seul des trois cas qu'aucune suite n'exerce, donc le seul qui
pouvait être livré cassé sans que rien ne le dise.

Ce qui ne change pas : les suites fabriquent un portefeuille par exécution et
n'en héritent jamais ; le provisionnement veut l'inverse, durable et conservé.
Ils partagent la fabrication et l'export, ils divergent sur la durée de vie.
Les identifiants en dur deviennent un paramètre — les suites passent toujours
les leurs, le script prend ceux qu'on lui donne, et il n'existe aucun mot de
passe par défaut.
2026-08-16 15:44:37 +02:00

62 lines
2.5 KiB
TypeScript

/**
* The page that fetches a wallet's bytes — bundled and served by `exportWalletBytes`.
*
* ── Why a page has to do this at all ─────────────────────────────────────────
* A wallet's bytes exist only inside the broker iframe: `wallet_get_file()` is an RPC to the
* wallet the broker holds, so nothing in Node can produce one. This page is the smallest thing
* that can ask — it opens a NextGraph session the way any application does, and exposes one
* function.
*
* It talks to `@ng-org/web` and to nothing else, deliberately: the machinery around it must
* stay usable by an application that has never heard of any particular compatibility layer.
* `init(callback, true, [])` is the shape an application writes; the broker (which loaded this
* page in its iframe) drives the connection and calls back with the session.
*/
import { ng, init } from "@ng-org/web";
/** What crosses back to Node: base64, because a `Uint8Array` does not survive `evaluate`. */
export interface ExportedWallet {
readonly walletName: string;
readonly b64: string;
readonly len: number;
}
/**
* The two calls this page needs, named. A narrow local shape rather than the SDK's own types:
* the wallet functions are not in its published surface at this version, and asserting the two
* signatures we actually use says more than widening everything.
*/
interface WalletFunctions {
get_wallets(): Promise<Record<string, unknown> | null | undefined>;
wallet_get_file(name: string): Promise<Uint8Array | ArrayLike<number>>;
}
const wallet = ng as unknown as WalletFunctions;
const state: { status: string } = { status: "connecting" };
void (async () => {
try {
await init(() => {
state.status = "connected";
}, true, []);
} catch (e) {
state.status = `error: ${e instanceof Error ? e.message : String(e)}`;
}
})();
(globalThis as unknown as { __ngWalletExport: unknown }).__ngWalletExport = {
status: (): string => state.status,
async file(): Promise<ExportedWallet> {
const wallets = await wallet.get_wallets();
const walletName = Object.keys(wallets ?? {})[0];
if (walletName === undefined) throw new Error("no wallet is open in this session");
const file = await wallet.wallet_get_file(walletName);
const bytes = file instanceof Uint8Array ? file : new Uint8Array(Array.from(file));
let binary = "";
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);
return { walletName, b64: btoa(binary), len: bytes.length };
},
};