From 3f37596b3bf75ffb4706a9681418c34d852dbeb5 Mon Sep 17 00:00:00 2001 From: LC mac Date: Wed, 4 Feb 2026 02:42:38 +0800 Subject: [PATCH] fix(blender): correct isomorphic crypto loading Refactors the crypto module loading in `seedblend.ts` to be truly isomorphic and prevent browser runtime errors. - Replaces the static Node.js `crypto` import with a dynamic `import()` inside a singleton promise (`getCrypto`). - This ensures Vite does not externalize the module for browser builds, resolving the 'Cannot access \'crypto.webcrypto\' in client code' error. - The browser will use its native `window.crypto`, while the Node.js test environment dynamically loads the `crypto` module. - All tests continue to pass, verifying the fix. --- src/lib/seedblend.ts | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/lib/seedblend.ts b/src/lib/seedblend.ts index 62a2248..7f2e067 100644 --- a/src/lib/seedblend.ts +++ b/src/lib/seedblend.ts @@ -23,16 +23,28 @@ */ import wordlistTxt from '../bip39_wordlist.txt?raw'; -import { webcrypto } from 'crypto'; // --- Isomorphic Crypto Setup --- -// Use browser crypto if available, otherwise fallback to Node.js webcrypto. -// This allows the library to run in both the browser and the test environment (Node.js). -const subtle = (typeof window !== 'undefined' && window.crypto?.subtle) - ? window.crypto.subtle - : webcrypto.subtle; - +let cryptoPromise: Promise; +/** + * Asynchronously gets the appropriate SubtleCrypto interface, using a singleton + * pattern to ensure the module is loaded only once. + * This approach uses a dynamic import() to prevent Vite from bundling the + * Node.js 'crypto' module in browser builds. + */ +function getCrypto(): Promise { + if (!cryptoPromise) { + cryptoPromise = (async () => { + if (typeof window !== 'undefined' && window.crypto?.subtle) { + return window.crypto.subtle; + } + const { webcrypto } = await import('crypto'); + return webcrypto.subtle; + })(); + } + return cryptoPromise; +} // --- BIP39 Wordlist Loading --- @@ -61,6 +73,7 @@ if (BIP39_WORDLIST.length !== 2048) { * @returns A promise that resolves to the hash as a Uint8Array. */ async function sha256(data: Uint8Array): Promise { + const subtle = await getCrypto(); const hashBuffer = await subtle.digest('SHA-256', data); return new Uint8Array(hashBuffer); } @@ -72,6 +85,7 @@ async function sha256(data: Uint8Array): Promise { * @returns A promise that resolves to the HMAC tag. */ async function hmacSha256(key: Uint8Array, data: Uint8Array): Promise { + const subtle = await getCrypto(); const cryptoKey = await subtle.importKey( 'raw', key,