diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cdf5c6110b..aa593319cd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -67,8 +67,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: "20" - - name: Check React SDK version sync - run: node scripts/check-react-sdk-sync.js - name: Install dependencies run: ./scripts/retry-yarn-install.sh packages/react-sdk - name: Run lint diff --git a/.gitignore b/.gitignore index 395f76d90f..392846c48b 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,9 @@ miden-client.toml !packages/react-sdk/ !packages/react-sdk/** packages/react-sdk/dist/ +!packages/vite-plugin/ +!packages/vite-plugin/** +packages/vite-plugin/dist/ keystore/ # Ignore files that spawn using `cargo run` diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c8634e500..145e1aeee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ * [FEATURE][web] New `MidenClient` class with resource-based API (`client.accounts`, `client.transactions`, `client.notes`, `client.tags`, `client.settings`). Provides high-level transaction helpers (`send`, `mint`, `consume`, `swap`, `consumeAll`), transaction dry-runs via `preview()`, confirmation polling via `waitFor()`, and flexible account/note references that accept hex strings, bech32 strings, or WASM objects interchangeably (`AccountRef`, `NoteInput` types). Factory methods: `MidenClient.create()`, `MidenClient.createTestnet()`, `MidenClient.createMock()`. ([#1762](https://github.com/0xMiden/miden-client/pull/1762)) * [FEATURE][web] Added `TransactionId.fromHex()` static constructor for creating transaction IDs from hex strings. ([#1762](https://github.com/0xMiden/miden-client/pull/1762)) * [FEATURE][web] Added standalone tree-shakeable note utilities (`createP2IDNote`, `createP2IDENote`, `buildSwapTag`) usable without a client instance. ([#1762](https://github.com/0xMiden/miden-client/pull/1762)) +* [FEATURE][web] Opt-in passkey-based encryption for secret keys at rest using WebAuthn PRF (Touch ID / Face ID / Windows Hello). Keys are encrypted with AES-256-GCM using a wrapping key derived from the authenticator's PRF output via HKDF-SHA256. Enable via `MidenClient.create({ passkeyEncryption: true })` or `MidenProvider config={{ passkeyEncryption: true }}`. Includes `isPasskeyPrfSupported()` for feature detection, transparent migration from plaintext keys, and a separate `MidenKeystore_*` IndexedDB for encrypted storage. Requires Chrome 116+, Safari 18+, or Edge 116+. ([#1836](https://github.com/0xMiden/miden-client/pull/1836)) ## 0.13.1 (TBD) diff --git a/crates/rust-client/src/rpc/domain/account.rs b/crates/rust-client/src/rpc/domain/account.rs index f70ee92944..cc1b609787 100644 --- a/crates/rust-client/src/rpc/domain/account.rs +++ b/crates/rust-client/src/rpc/domain/account.rs @@ -671,8 +671,7 @@ impl From for Vec Vec { - use account_detail_request; - use account_detail_request::storage_map_detail_request; + use account_detail_request::{self, storage_map_detail_request}; let request_map = value.0; let mut requests = Vec::with_capacity(request_map.len()); for (slot_name, _map_keys) in request_map { diff --git a/crates/web-client/js/client.js b/crates/web-client/js/client.js index e7ea291253..83da48b044 100644 --- a/crates/web-client/js/client.js +++ b/crates/web-client/js/client.js @@ -53,6 +53,36 @@ export class MidenClient { const seed = options?.seed ? await hashSeed(options.seed) : undefined; + // Resolve passkey encryption → keystore callbacks (before the keystore branch) + if (options?.passkeyEncryption && options?.keystore) { + console.warn( + "Both passkeyEncryption and keystore provided; keystore takes precedence." + ); + } + if (options?.passkeyEncryption && !options?.keystore) { + const { createPasskeyKeystore, isPasskeyPrfSupported } = + await import("./passkey-keystore.js"); + if (await isPasskeyPrfSupported()) { + const passkeyOpts = + typeof options.passkeyEncryption === "object" + ? options.passkeyEncryption + : {}; + const storeName = options?.storeName || "default"; + const result = await createPasskeyKeystore(storeName, passkeyOpts); + options = { + ...options, + storeName, + keystore: { getKey: result.getKey, insertKey: result.insertKey }, + }; + } + // Unsupported browser — fall through to standard keystore + else { + console.warn( + "passkeyEncryption was requested but WebAuthn PRF is not supported in this browser. Falling back to standard keystore." + ); + } + } + let inner; if (options?.keystore) { inner = await WebClientClass.createClientWithExternalKeystore( diff --git a/crates/web-client/js/index.js b/crates/web-client/js/index.js index e8c2137c12..6e0d57d069 100644 --- a/crates/web-client/js/index.js +++ b/crates/web-client/js/index.js @@ -28,6 +28,10 @@ export const AuthScheme = Object.freeze({ export { MidenClient }; export { createP2IDNote, createP2IDENote, buildSwapTag }; +export { + isPasskeyPrfSupported, + createPasskeyKeystore, +} from "./passkey-keystore.js"; // Internal exports — used by integration tests that need direct access to the low-level WebClient proxy. export { WebClient as WasmWebClient, MockWebClient as MockWasmWebClient }; diff --git a/crates/web-client/js/passkey-keystore.js b/crates/web-client/js/passkey-keystore.js new file mode 100644 index 0000000000..8fcc9299ca --- /dev/null +++ b/crates/web-client/js/passkey-keystore.js @@ -0,0 +1,610 @@ +/** + * WebAuthn PRF-based key encryption for Miden web client. + * + * Provides opt-in passkey encryption for secret keys at rest using the WebAuthn + * PRF extension (Touch ID, Face ID, Windows Hello). Keys are wrapped with + * AES-256-GCM using a wrapping key derived from the authenticator's PRF output. + * + * Browser support: Chrome 116+, Safari 18+, Edge 116+. Firefox does NOT support PRF. + * + * @module passkey-keystore + */ + +import Dexie from "dexie"; + +// ════════════════════════════════════════════════════════════════ +// Constants +// ════════════════════════════════════════════════════════════════ + +/** Magic bytes for web-encrypted payloads. Distinct from native "MENC" (ChaCha20-Poly1305). */ +const ENCRYPTED_MAGIC = new Uint8Array([0x4d, 0x57, 0x45, 0x42]); // "MWEB" + +/** Format version byte. Bump on breaking format changes. */ +const ENCRYPTED_VERSION = 0x01; + +/** Fixed HKDF salt — must never change (needed to reproduce wrapping key across sessions). */ +const HKDF_SALT = new TextEncoder().encode("miden-client-passkey-v1"); + +/** Fixed HKDF info — domain separation for the wrapping key derivation. */ +const HKDF_INFO = new TextEncoder().encode("aes-256-gcm-wrapping-key"); + +/** Fixed salt for WebAuthn PRF evaluation. */ +const PRF_EVAL_SALT = new TextEncoder().encode("miden-passkey-prf-salt-v1"); + +/** AES-GCM IV length in bytes. */ +const IV_LEN = 12; + +/** Header length: magic (4) + version (1) + IV (12) = 17 bytes. */ +const HEADER_LEN = 4 + 1 + IV_LEN; + +/** localStorage key prefix for credential IDs. */ +const CREDENTIAL_STORAGE_PREFIX = "miden_passkey_credential_"; + +// ════════════════════════════════════════════════════════════════ +// Feature detection +// ════════════════════════════════════════════════════════════════ + +/** + * Returns `true` if the current browser supports WebAuthn with the PRF extension. + * + * Checks for: + * 1. `PublicKeyCredential` API availability + * 2. Platform authenticator availability + * 3. PRF extension support (via `getClientCapabilities` or fallback heuristic) + * + * @returns {Promise} + */ +export async function isPasskeyPrfSupported() { + try { + if ( + typeof window === "undefined" || + !window.PublicKeyCredential || + !navigator.credentials + ) { + return false; + } + + // Check platform authenticator + if ( + typeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === + "function" + ) { + const available = + await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); + if (!available) return false; + } + + // Prefer getClientCapabilities if available (Chrome 132+) + if (typeof PublicKeyCredential.getClientCapabilities === "function") { + const capabilities = await PublicKeyCredential.getClientCapabilities(); + return capabilities?.["extension:prf"] === true; + } + + // Fallback: check user agent for known-supporting browsers + const ua = navigator.userAgent; + // Chrome/Edge 116+ support PRF + const chromeMatch = ua.match(/Chrom(?:e|ium)\/(\d+)/); + if (chromeMatch && parseInt(chromeMatch[1], 10) >= 116) return true; + // Safari 18+ supports PRF + const safariMatch = ua.match(/Version\/(\d+).*Safari/); + if (safariMatch && parseInt(safariMatch[1], 10) >= 18) return true; + + return false; + } catch { + return false; + } +} + +// ════════════════════════════════════════════════════════════════ +// WebAuthn operations +// ════════════════════════════════════════════════════════════════ + +/** + * Registers a new passkey with PRF extension support. + * + * @param {object} options + * @param {string} [options.rpId] - Relying party ID. Defaults to current hostname. + * @param {string} [options.rpName] - Relying party display name. Defaults to "Miden Client". + * @param {string} [options.userName] - User display name. Defaults to "Miden Wallet User". + * @returns {Promise<{ credentialId: string, prfOutput: ArrayBuffer }>} + */ +async function registerPasskey(options = {}) { + const rpId = options.rpId || window.location.hostname; + const rpName = options.rpName || "Miden Client"; + const userName = options.userName || "Miden Wallet User"; + + // Generate a random user ID (not secret, just needs to be unique) + const userId = crypto.getRandomValues(new Uint8Array(32)); + + const credential = await navigator.credentials.create({ + publicKey: { + rp: { id: rpId, name: rpName }, + user: { + id: userId, + name: userName, + displayName: userName, + }, + challenge: crypto.getRandomValues(new Uint8Array(32)), + pubKeyCredParams: [ + { alg: -7, type: "public-key" }, // ES256 + { alg: -257, type: "public-key" }, // RS256 + ], + authenticatorSelection: { + authenticatorAttachment: "platform", + residentKey: "required", + userVerification: "required", + }, + extensions: { + prf: { + eval: { first: PRF_EVAL_SALT }, + }, + }, + }, + }); + + const prfResults = credential.getClientExtensionResults()?.prf; + if (!prfResults?.results?.first) { + throw new Error( + "WebAuthn PRF extension not supported by this authenticator. " + + "Use isPasskeyPrfSupported() to check before enabling passkey encryption." + ); + } + + const credentialId = bufferToBase64Url(credential.rawId); + return { + credentialId, + prfOutput: prfResults.results.first, + }; +} + +/** + * Authenticates with an existing passkey and evaluates the PRF extension. + * + * @param {string} credentialId - Base64url-encoded credential ID. + * @param {string} [rpId] - Relying party ID. Defaults to current hostname. + * @returns {Promise} The PRF output (32 bytes). + */ +async function authenticateWithPrf(credentialId, rpId) { + rpId = rpId || window.location.hostname; + const credentialIdBuffer = base64UrlToBuffer(credentialId); + + const assertion = await navigator.credentials.get({ + publicKey: { + challenge: crypto.getRandomValues(new Uint8Array(32)), + rpId, + allowCredentials: [ + { + id: credentialIdBuffer, + type: "public-key", + transports: ["internal"], + }, + ], + userVerification: "required", + extensions: { + prf: { + eval: { first: PRF_EVAL_SALT }, + }, + }, + }, + }); + + const prfResults = assertion.getClientExtensionResults()?.prf; + if (!prfResults?.results?.first) { + throw new Error( + "PRF evaluation failed. The authenticator did not return a PRF result." + ); + } + + return prfResults.results.first; +} + +// ════════════════════════════════════════════════════════════════ +// Key derivation & encryption +// ════════════════════════════════════════════════════════════════ + +/** + * Derives a non-extractable AES-256-GCM wrapping key from PRF output via HKDF-SHA256. + * + * @param {ArrayBuffer} prfOutput - Raw PRF output from the authenticator. + * @returns {Promise} Non-extractable AES-256-GCM key. + */ +async function deriveWrappingKey(prfOutput) { + if (prfOutput.byteLength < 32) { + throw new Error( + `PRF output too short: expected at least 32 bytes, got ${prfOutput.byteLength}. ` + + "The authenticator may not support sufficient entropy for key derivation." + ); + } + + const keyMaterial = await crypto.subtle.importKey( + "raw", + prfOutput, + "HKDF", + false, + ["deriveKey"] + ); + + return crypto.subtle.deriveKey( + { + name: "HKDF", + hash: "SHA-256", + salt: HKDF_SALT, + info: HKDF_INFO, + }, + keyMaterial, + { name: "AES-GCM", length: 256 }, + false, // non-extractable + ["encrypt", "decrypt"] + ); +} + +/** + * Encrypts a secret key using AES-256-GCM with the MWEB envelope format. + * + * Format: [4B: "MWEB"] [1B: version=0x01] [12B: IV] [NB: ciphertext + 16B auth tag] + * + * The pub key commitment bytes are used as AES-GCM additional authenticated data (AAD), + * binding each ciphertext to its corresponding public key. This prevents ciphertext-swapping + * attacks where an attacker rearranges entries in IndexedDB. + * + * @param {CryptoKey} wrappingKey - AES-256-GCM wrapping key. + * @param {Uint8Array} plaintext - Secret key bytes to encrypt. + * @param {Uint8Array} pubKeyCommitment - Public key commitment bytes (used as AAD). + * @returns {Promise} MWEB-envelope ciphertext. + */ +async function encryptSecretKey(wrappingKey, plaintext, pubKeyCommitment) { + const iv = crypto.getRandomValues(new Uint8Array(IV_LEN)); + + const ciphertext = await crypto.subtle.encrypt( + { + name: "AES-GCM", + iv, + additionalData: pubKeyCommitment, + }, + wrappingKey, + plaintext + ); + + // Assemble: magic + version + iv + ciphertext + const output = new Uint8Array(HEADER_LEN + ciphertext.byteLength); + output.set(ENCRYPTED_MAGIC, 0); + output[4] = ENCRYPTED_VERSION; + output.set(iv, 5); + output.set(new Uint8Array(ciphertext), HEADER_LEN); + + return output; +} + +/** + * Decrypts an MWEB-envelope ciphertext using AES-256-GCM. + * + * @param {CryptoKey} wrappingKey - AES-256-GCM wrapping key. + * @param {Uint8Array} envelope - MWEB-envelope ciphertext. + * @param {Uint8Array} pubKeyCommitment - Public key commitment bytes (used as AAD). + * @returns {Promise} Decrypted secret key bytes. + */ +async function decryptSecretKey(wrappingKey, envelope, pubKeyCommitment) { + if (envelope.length < HEADER_LEN + 16) { + throw new Error("Encrypted payload too short"); + } + + // Validate magic + if ( + envelope[0] !== ENCRYPTED_MAGIC[0] || + envelope[1] !== ENCRYPTED_MAGIC[1] || + envelope[2] !== ENCRYPTED_MAGIC[2] || + envelope[3] !== ENCRYPTED_MAGIC[3] + ) { + throw new Error("Invalid encrypted payload: bad magic bytes"); + } + + // Validate version + const version = envelope[4]; + if (version !== ENCRYPTED_VERSION) { + throw new Error( + `Unsupported encryption format version: ${version}. ` + + `This client supports version ${ENCRYPTED_VERSION}. ` + + "You may need to update the SDK." + ); + } + + const iv = envelope.slice(5, 5 + IV_LEN); + const ciphertext = envelope.slice(HEADER_LEN); + + const plaintext = await crypto.subtle.decrypt( + { + name: "AES-GCM", + iv, + additionalData: pubKeyCommitment, + }, + wrappingKey, + ciphertext + ); + + return new Uint8Array(plaintext); +} + +// ════════════════════════════════════════════════════════════════ +// Credential persistence +// ════════════════════════════════════════════════════════════════ + +function getStoredCredentialId(storeName) { + return localStorage.getItem(CREDENTIAL_STORAGE_PREFIX + storeName); +} + +function storeCredentialId(storeName, credentialId) { + localStorage.setItem(CREDENTIAL_STORAGE_PREFIX + storeName, credentialId); +} + +// ════════════════════════════════════════════════════════════════ +// Encrypted key storage (separate Dexie database) +// ════════════════════════════════════════════════════════════════ + +/** + * Opens (or creates) the encrypted keystore database. + * + * Uses a separate Dexie database `MidenKeystore_${storeName}` to avoid + * schema conflicts with the main `MidenClientDB` database. + * + * @param {string} storeName + * @returns {Dexie} + */ +function openKeystoreDb(storeName) { + const db = new Dexie(`MidenKeystore_${storeName}`); + db.version(1).stores({ + keys: "pubKeyHex", + }); + return db; +} + +/** + * Opens the main client Dexie database for migration fallback reads. + * + * This reads directly from the idxdb-store's `accountAuth` table, which + * stores plaintext secret keys keyed by `pubKeyCommitmentHex`. + * + * The DB name must match what the WASM/Rust side uses. When storeName is + * explicitly provided by the user, it is used as-is. We cannot determine + * the auto-generated name (`MidenClientDB_{network_id}`) from JS because + * that requires the WASM client to have been initialized first. + * + * @param {string} storeName - The store name as passed to MidenClient.create(). + * @returns {Dexie | null} The Dexie DB, or null if storeName is unknown. + */ +function openMainDbForMigration(storeName) { + if (!storeName) return null; + const db = new Dexie(storeName); + // Declare just the table we need — Dexie allows partial schema declarations + // for read-only access to existing databases. + db.version(1).stores({ + accountAuth: "pubKeyCommitmentHex", + }); + return db; +} + +// ════════════════════════════════════════════════════════════════ +// Base64url utilities +// ════════════════════════════════════════════════════════════════ + +function bufferToBase64Url(buffer) { + const bytes = new Uint8Array(buffer); + let binary = ""; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); +} + +function base64UrlToBuffer(base64url) { + const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/"); + const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4); + const binary = atob(padded); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes.buffer; +} + +function bytesToHex(bytes) { + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +function hexToBytes(hex) { + if (hex.length % 2 !== 0) { + throw new Error("Hex string must have even length"); + } + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < bytes.length; i++) { + const byte = parseInt(hex.slice(i * 2, i * 2 + 2), 16); + if (Number.isNaN(byte)) { + throw new Error(`Invalid hex character at position ${i * 2}`); + } + bytes[i] = byte; + } + return bytes; +} + +// ════════════════════════════════════════════════════════════════ +// Factory +// ════════════════════════════════════════════════════════════════ + +/** + * Creates a passkey-encrypted keystore backed by WebAuthn PRF. + * + * If no `credentialId` is provided and none exists in localStorage for the given + * store name, a new passkey is registered (triggering a biometric prompt for + * registration). If a credential ID is found (either provided or in localStorage), + * the existing passkey is used (triggering a biometric prompt for authentication). + * + * The returned `getKey` and `insertKey` callbacks are compatible with the + * `ClientOptions.keystore` interface and transparently encrypt/decrypt keys + * using the PRF-derived wrapping key. + * + * **Migration**: When `getKey` finds no encrypted entry in the keystore DB but + * `storeName` is explicitly provided, it attempts to read the plaintext key from + * the main client database's `accountAuth` table. If found, the key is + * transparently re-encrypted and migrated to the keystore DB. Migration is only + * available when `storeName` is explicitly provided (since the auto-generated DB + * name is not known to JS before WASM initialization). + * + * @param {string} storeName - Store isolation key (from ClientOptions.storeName). + * @param {object} [options] + * @param {string} [options.credentialId] - Existing credential ID (base64url). + * @param {string} [options.rpId] - WebAuthn relying party ID. + * @param {string} [options.rpName] - Relying party display name. + * @param {string} [options.userName] - User display name for the passkey. + * @returns {Promise<{ getKey: Function, insertKey: Function, credentialId: string }>} + */ +export async function createPasskeyKeystore(storeName, options = {}) { + if (!storeName || typeof storeName !== "string") { + throw new Error("storeName is required and must be a non-empty string"); + } + + // Check browser support + const supported = await isPasskeyPrfSupported(); + if (!supported) { + throw new Error( + "WebAuthn PRF extension is not supported in this browser. " + + "Use isPasskeyPrfSupported() to check before enabling passkeyEncryption. " + + "Supported browsers: Chrome 116+, Safari 18+, Edge 116+." + ); + } + + // Resolve credential ID: provided > localStorage > register new + let credentialId = options.credentialId || getStoredCredentialId(storeName); + let prfOutput; + + if (credentialId) { + // Authenticate with existing passkey + prfOutput = await authenticateWithPrf(credentialId, options.rpId); + } else { + // Register new passkey + const result = await registerPasskey({ + rpId: options.rpId, + rpName: options.rpName, + userName: options.userName, + }); + credentialId = result.credentialId; + prfOutput = result.prfOutput; + } + + // Persist credential ID for future sessions + storeCredentialId(storeName, credentialId); + + // Derive wrapping key (non-extractable, held in closure) + const wrappingKey = await deriveWrappingKey(prfOutput); + + // Open encrypted keystore database + const keystoreDb = openKeystoreDb(storeName); + + // Open main DB for migration (only when storeName is explicitly provided) + const mainDb = openMainDbForMigration(storeName); + + /** + * getKey callback — decrypts the secret key for a given pub key commitment. + * + * The `pubKey` parameter contains the pub key commitment bytes (RPO256 hash + * of the public key), NOT the raw public key. This is a 32-byte Uint8Array. + * + * @param {Uint8Array} pubKey - Public key commitment bytes (32 bytes). + * @returns {Promise} + */ + async function getKey(pubKey) { + const pubKeyHex = bytesToHex(pubKey); + + // Try encrypted keystore first + const record = await keystoreDb.keys.get(pubKeyHex); + if (record) { + const ciphertext = hexToBytes(record.ciphertextHex); + return await decryptSecretKey(wrappingKey, ciphertext, pubKey); + } + + // Migration fallback: try main DB for plaintext keys + if (mainDb) { + try { + const authRecord = await mainDb + .table("accountAuth") + .where("pubKeyCommitmentHex") + .equals(pubKeyHex) + .first(); + + if (authRecord?.secretKeyHex) { + const plaintext = hexToBytes(authRecord.secretKeyHex); + + // Re-encrypt and migrate to keystore DB + const encrypted = await encryptSecretKey( + wrappingKey, + plaintext, + pubKey + ); + await keystoreDb.keys.put({ + pubKeyHex, + ciphertextHex: bytesToHex(encrypted), + }); + + // Verify round-trip before deleting plaintext — if decryption + // fails or bytes don't match, the plaintext entry is preserved. + const verifyRecord = await keystoreDb.keys.get(pubKeyHex); + const verifyCt = hexToBytes(verifyRecord.ciphertextHex); + const decrypted = await decryptSecretKey( + wrappingKey, + verifyCt, + pubKey + ); + if ( + decrypted.length !== plaintext.length || + !decrypted.every((b, i) => b === plaintext[i]) + ) { + throw new Error("Migration round-trip verification failed"); + } + + // Remove plaintext from old DB after successful verification + try { + await mainDb + .table("accountAuth") + .where("pubKeyCommitmentHex") + .equals(pubKeyHex) + .delete(); + } catch { + // Best-effort cleanup — old DB schema may differ + } + + return plaintext; + } + } catch (e) { + // Main DB may not exist, have different schema, or be at a higher + // Dexie version — migration is best-effort, not required. + console.debug("Passkey migration: could not read from main DB", e); + } + } + + return undefined; + } + + /** + * insertKey callback — encrypts and stores the secret key. + * + * The `pubKey` parameter contains the pub key commitment bytes (RPO256 hash + * of the public key), NOT the raw public key. This is a 32-byte Uint8Array. + * + * @param {Uint8Array} pubKey - Public key commitment bytes (32 bytes). + * @param {Uint8Array} secretKey - Secret key bytes to encrypt. + * @returns {Promise} + */ + async function insertKey(pubKey, secretKey) { + const pubKeyHex = bytesToHex(pubKey); + const encrypted = await encryptSecretKey(wrappingKey, secretKey, pubKey); + + // Upsert: put() overwrites existing entries. This is intentional — + // the WASM side may re-insert during migration or key rotation. + await keystoreDb.keys.put({ + pubKeyHex, + ciphertextHex: bytesToHex(encrypted), + }); + } + + return { getKey, insertKey, credentialId }; +} diff --git a/crates/web-client/js/types/api-types.d.ts b/crates/web-client/js/types/api-types.d.ts index d25b87587f..d79cde1312 100644 --- a/crates/web-client/js/types/api-types.d.ts +++ b/crates/web-client/js/types/api-types.d.ts @@ -95,6 +95,21 @@ export type AccountTypeValue = | "ImmutableWallet" | "FungibleFaucet"; +// ════════════════════════════════════════════════════════════════ +// Passkey encryption options +// ════════════════════════════════════════════════════════════════ + +export interface PasskeyEncryptionOptions { + /** Existing credential ID (base64url). Omit to register a new passkey. */ + credentialId?: string; + /** WebAuthn relying party ID. Defaults to current hostname. */ + rpId?: string; + /** Relying party display name. Defaults to "Miden Client". */ + rpName?: string; + /** User display name for the passkey. Defaults to "Miden Wallet User". */ + userName?: string; +} + // ════════════════════════════════════════════════════════════════ // Client options // ════════════════════════════════════════════════════════════════ @@ -116,8 +131,24 @@ export interface ClientOptions { keystore?: { getKey: GetKeyCallback; insertKey: InsertKeyCallback; - sign: SignCallback; + /** + * Optional signing callback. When omitted, the Rust/WASM side calls `getKey` + * to retrieve the secret key and signs locally. Only provide this if you need + * signing to happen outside of WASM (e.g., in a remote HSM). + */ + sign?: SignCallback; }; + /** + * Opt-in passkey encryption for keys at rest. Pass `true` for defaults + * or a `PasskeyEncryptionOptions` object to reuse an existing credential. + * + * When `true`, checks localStorage for an existing credential and reuses it + * if found; otherwise registers a new passkey (triggering a biometric prompt). + * + * Requires Chrome 116+, Safari 18+, or Edge 116+. Firefox does NOT support PRF. + * Mutually exclusive with `keystore` — if both are provided, `keystore` takes precedence. + */ + passkeyEncryption?: boolean | PasskeyEncryptionOptions; } // ════════════════════════════════════════════════════════════════ @@ -519,3 +550,37 @@ export declare function buildSwapTag(options: BuildSwapTagOptions): NoteTag; /** Returns the initialized WASM module. Throws if WASM is unavailable. */ export declare function getWasmOrThrow(): Promise; + +// ════════════════════════════════════════════════════════════════ +// Passkey utilities (tree-shakeable) +// ════════════════════════════════════════════════════════════════ + +/** + * Returns `true` if the current browser supports WebAuthn with the PRF extension, + * which is required for passkey-based key encryption. + * + * Supported browsers: Chrome 116+, Safari 18+, Edge 116+. Firefox does NOT support PRF. + */ +export declare function isPasskeyPrfSupported(): Promise; + +/** Result of `createPasskeyKeystore()`. */ +export interface PasskeyKeystore { + /** Decrypts and returns the secret key for a given pub key commitment. */ + getKey: GetKeyCallback; + /** Encrypts and stores a secret key for a given pub key commitment. */ + insertKey: InsertKeyCallback; + /** The credential ID (base64url) of the passkey used for this keystore. */ + credentialId: string; +} + +/** + * Creates a passkey-encrypted keystore backed by WebAuthn PRF. + * + * Registers a new passkey or authenticates with an existing one (biometric prompt), + * then returns `getKey`/`insertKey` callbacks that transparently encrypt/decrypt + * secret keys using the PRF-derived wrapping key. + */ +export declare function createPasskeyKeystore( + storeName: string, + options?: PasskeyEncryptionOptions +): Promise; diff --git a/crates/web-client/rollup.config.js b/crates/web-client/rollup.config.js index 6f338c4d69..cf33d70597 100644 --- a/crates/web-client/rollup.config.js +++ b/crates/web-client/rollup.config.js @@ -83,6 +83,16 @@ export default [ commonjs(), ], }, + // Build the passkey-keystore as a standalone module (used by tests and direct imports) + { + input: "./js/passkey-keystore.js", + output: { + dir: `dist`, + format: "es", + sourcemap: true, + }, + plugins: [resolve(), commonjs()], + }, // Build the worker file { input: "./js/workers/web-client-methods-worker.js", diff --git a/crates/web-client/test/passkey-keystore.test.ts b/crates/web-client/test/passkey-keystore.test.ts new file mode 100644 index 0000000000..6bc2fcd416 --- /dev/null +++ b/crates/web-client/test/passkey-keystore.test.ts @@ -0,0 +1,652 @@ +import { + test as base, + expect, + BrowserContext, + CDPSession, +} from "@playwright/test"; +import { RUN_ID, getRpcUrl } from "./playwright.global.setup"; + +/** + * Passkey keystore tests using Chrome DevTools Protocol virtual authenticator. + * + * These tests use CDP to create a virtual authenticator with PRF extension support, + * allowing us to test the full passkey encryption flow without real hardware. + * + * Only runs on Chromium (CDP is not available on WebKit/Firefox). + */ + +let cdpSession: CDPSession; +let authenticatorId: string; + +function generateStoreName(suffix: string): string { + return `test_passkey_${RUN_ID}_${suffix}`; +} + +// Custom test fixture that sets up a virtual authenticator with PRF support +const test = base.extend<{ forEachTest: void }>({ + forEachTest: [ + async ({ page, browserName }, use) => { + // Virtual authenticators with PRF require CDP (Chromium only) + if (browserName !== "chromium") { + test.skip(); + return; + } + + cdpSession = await page.context().newCDPSession(page); + + // Enable the virtual authenticator environment + await cdpSession.send("WebAuthn.enable", { + enableUI: false, + }); + + // Add a virtual authenticator with PRF extension support + const result = await cdpSession.send("WebAuthn.addVirtualAuthenticator", { + options: { + protocol: "ctap2", + transport: "internal", + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + hasPrf: true, + }, + }); + authenticatorId = result.authenticatorId; + + await page.goto("http://localhost:8080"); + + // Import the SDK + await page.evaluate(async () => { + const sdkExports = await import("./index.js"); + for (const [key, value] of Object.entries(sdkExports)) { + (window as any)[key] = value; + } + }); + + await use(); + + // Cleanup + await cdpSession.send("WebAuthn.removeVirtualAuthenticator", { + authenticatorId, + }); + await cdpSession.send("WebAuthn.disable"); + }, + { auto: true }, + ], +}); + +test.describe("passkey keystore", () => { + test("isPasskeyPrfSupported returns true with virtual authenticator", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const supported = await page.evaluate(async () => { + return await (window as any).isPasskeyPrfSupported(); + }); + + expect(supported).toBe(true); + }); + + test("createPasskeyKeystore registers a new passkey and returns callbacks", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("register"); + + const result = await page.evaluate(async (storeName) => { + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + const keystore = await createPasskeyKeystore(storeName); + return { + hasGetKey: typeof keystore.getKey === "function", + hasInsertKey: typeof keystore.insertKey === "function", + hasCredentialId: typeof keystore.credentialId === "string", + credentialIdLength: keystore.credentialId.length, + }; + }, storeName); + + expect(result.hasGetKey).toBe(true); + expect(result.hasInsertKey).toBe(true); + expect(result.hasCredentialId).toBe(true); + expect(result.credentialIdLength).toBeGreaterThan(0); + }); + + test("credential ID is persisted to localStorage and reused", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("persist"); + + const { credentialId1, credentialId2 } = await page.evaluate( + async (storeName) => { + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + + // First call — registers a new passkey + const ks1 = await createPasskeyKeystore(storeName); + const credentialId1 = ks1.credentialId; + + // Second call — should reuse the same credential from localStorage + const ks2 = await createPasskeyKeystore(storeName); + const credentialId2 = ks2.credentialId; + + return { credentialId1, credentialId2 }; + }, + storeName + ); + + expect(credentialId1).toBe(credentialId2); + }); + + test("insertKey encrypts and getKey decrypts round-trip", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("roundtrip"); + + const result = await page.evaluate(async (storeName) => { + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + const keystore = await createPasskeyKeystore(storeName); + + // Create fake pub key commitment (32 bytes) and secret key + const pubKey = new Uint8Array(32); + crypto.getRandomValues(pubKey); + const secretKey = new Uint8Array(64); + crypto.getRandomValues(secretKey); + + // Insert (encrypts) + await keystore.insertKey(pubKey, secretKey); + + // Get (decrypts) + const retrieved = await keystore.getKey(pubKey); + + return { + original: Array.from(secretKey), + retrieved: retrieved ? Array.from(retrieved) : null, + }; + }, storeName); + + expect(result.retrieved).toEqual(result.original); + }); + + test("getKey returns undefined for unknown key", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("unknown"); + + const result = await page.evaluate(async (storeName) => { + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + const keystore = await createPasskeyKeystore(storeName); + + const unknownPubKey = new Uint8Array(32); + crypto.getRandomValues(unknownPubKey); + return await keystore.getKey(unknownPubKey); + }, storeName); + + expect(result).toBeUndefined(); + }); + + test("encrypted data in IndexedDB starts with MWEB magic", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("magic"); + + const magicBytes = await page.evaluate(async (storeName) => { + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + const keystore = await createPasskeyKeystore(storeName); + + const pubKey = new Uint8Array(32); + crypto.getRandomValues(pubKey); + const secretKey = new Uint8Array(64); + crypto.getRandomValues(secretKey); + + await keystore.insertKey(pubKey, secretKey); + + // Read raw data from IndexedDB to verify MWEB magic + const pubKeyHex = Array.from(pubKey) + .map((b: number) => b.toString(16).padStart(2, "0")) + .join(""); + const record = await new Promise((resolve, reject) => { + const req = indexedDB.open(`MidenKeystore_${storeName}`); + req.onsuccess = () => { + const tx = req.result.transaction("keys", "readonly"); + const getReq = tx.objectStore("keys").get(pubKeyHex); + getReq.onsuccess = () => resolve(getReq.result); + getReq.onerror = () => reject(getReq.error); + }; + req.onerror = () => reject(req.error); + }); + const ciphertextHex = record?.ciphertextHex as string; + + // First 4 bytes (8 hex chars) should be "MWEB" = 4d574542 + return ciphertextHex?.substring(0, 8); + }, storeName); + + expect(magicBytes).toBe("4d574542"); + }); + + test("different pub keys produce different ciphertexts", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("unique-ct"); + + const result = await page.evaluate(async (storeName) => { + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + const keystore = await createPasskeyKeystore(storeName); + + const secretKey = new Uint8Array(64); + crypto.getRandomValues(secretKey); + + const pubKey1 = new Uint8Array(32); + crypto.getRandomValues(pubKey1); + const pubKey2 = new Uint8Array(32); + crypto.getRandomValues(pubKey2); + + await keystore.insertKey(pubKey1, secretKey); + await keystore.insertKey(pubKey2, secretKey); + + // Read raw ciphertexts from IndexedDB + const hex1 = Array.from(pubKey1) + .map((b: number) => b.toString(16).padStart(2, "0")) + .join(""); + const hex2 = Array.from(pubKey2) + .map((b: number) => b.toString(16).padStart(2, "0")) + .join(""); + + const readRecord = (key: string) => + new Promise((resolve, reject) => { + const req = indexedDB.open(`MidenKeystore_${storeName}`); + req.onsuccess = () => { + const tx = req.result.transaction("keys", "readonly"); + const getReq = tx.objectStore("keys").get(key); + getReq.onsuccess = () => resolve(getReq.result); + getReq.onerror = () => reject(getReq.error); + }; + req.onerror = () => reject(req.error); + }); + + const record1 = await readRecord(hex1); + const record2 = await readRecord(hex2); + + return { + ct1: record1?.ciphertextHex, + ct2: record2?.ciphertextHex, + }; + }, storeName); + + // Same plaintext with different AAD (pub key commitment) → different ciphertext + // Also, random IV ensures uniqueness + expect(result.ct1).not.toBe(result.ct2); + }); + + test("ciphertext bound to pub key commitment (AAD prevents swapping)", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("aad"); + + const decryptFailed = await page.evaluate(async (storeName) => { + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + const keystore = await createPasskeyKeystore(storeName); + + const pubKey1 = new Uint8Array(32); + crypto.getRandomValues(pubKey1); + const pubKey2 = new Uint8Array(32); + crypto.getRandomValues(pubKey2); + const secretKey = new Uint8Array(64); + crypto.getRandomValues(secretKey); + + await keystore.insertKey(pubKey1, secretKey); + + // Manually swap the ciphertext to pubKey2's entry in IndexedDB + const hex1 = Array.from(pubKey1) + .map((b: number) => b.toString(16).padStart(2, "0")) + .join(""); + const hex2 = Array.from(pubKey2) + .map((b: number) => b.toString(16).padStart(2, "0")) + .join(""); + + const dbName = `MidenKeystore_${storeName}`; + const record = await new Promise((resolve, reject) => { + const req = indexedDB.open(dbName); + req.onsuccess = () => { + const tx = req.result.transaction("keys", "readonly"); + const getReq = tx.objectStore("keys").get(hex1); + getReq.onsuccess = () => resolve(getReq.result); + getReq.onerror = () => reject(getReq.error); + }; + req.onerror = () => reject(req.error); + }); + // Put the same ciphertext under pubKey2 + await new Promise((resolve, reject) => { + const req = indexedDB.open(dbName); + req.onsuccess = () => { + const tx = req.result.transaction("keys", "readwrite"); + const putReq = tx.objectStore("keys").put({ + pubKeyHex: hex2, + ciphertextHex: record?.ciphertextHex, + }); + putReq.onsuccess = () => resolve(); + putReq.onerror = () => reject(putReq.error); + }; + req.onerror = () => reject(req.error); + }); + + // Attempting to decrypt with pubKey2 should fail (AAD mismatch) + try { + await keystore.getKey(pubKey2); + return false; // Should not reach here + } catch { + return true; // AES-GCM auth tag verification failed + } + }, storeName); + + expect(decryptFailed).toBe(true); + }); + + test("works with MidenClient.create via passkeyEncryption option", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("client-api"); + const rpcUrl = getRpcUrl(); + + const result = await page.evaluate( + async ({ storeName, rpcUrl }) => { + const MidenClient = (window as any).MidenClient; + const client = await MidenClient.create({ + rpcUrl, + storeName, + passkeyEncryption: true, + }); + return { + clientCreated: client != null, + hasAccounts: typeof client.accounts === "object", + hasTransactions: typeof client.transactions === "object", + }; + }, + { storeName, rpcUrl } + ); + + expect(result.clientCreated).toBe(true); + expect(result.hasAccounts).toBe(true); + expect(result.hasTransactions).toBe(true); + }); + + test("migration: reads plaintext key from main DB, encrypts, and removes plaintext", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("migrate"); + + const result = await page.evaluate(async (storeName) => { + // 1. Seed the main DB with a plaintext key using native IndexedDB + const pubKeyHex = "aa".repeat(32); // 32-byte fake pub key commitment as hex + const secretKeyHex = "bb".repeat(64); // 64-byte fake secret key as hex + + await new Promise((resolve, reject) => { + const req = indexedDB.open(storeName, 1); + req.onupgradeneeded = () => { + const db = req.result; + if (!db.objectStoreNames.contains("accountAuth")) { + db.createObjectStore("accountAuth", { + keyPath: "pubKeyCommitmentHex", + }); + } + }; + req.onsuccess = () => { + const db = req.result; + const tx = db.transaction("accountAuth", "readwrite"); + tx.objectStore("accountAuth").put({ + pubKeyCommitmentHex: pubKeyHex, + secretKeyHex, + }); + tx.oncomplete = () => { + db.close(); + resolve(); + }; + tx.onerror = () => reject(tx.error); + }; + req.onerror = () => reject(req.error); + }); + + // 2. Create passkey keystore — migration should kick in on getKey + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + const keystore = await createPasskeyKeystore(storeName); + + // 3. Convert hex pubKey to bytes for getKey call + const pubKeyBytes = new Uint8Array(32); + for (let i = 0; i < 32; i++) { + pubKeyBytes[i] = parseInt(pubKeyHex.slice(i * 2, i * 2 + 2), 16); + } + + const retrieved = await keystore.getKey(pubKeyBytes); + + // 4. Verify the returned key matches the original plaintext + const expectedBytes = new Uint8Array(64); + for (let i = 0; i < 64; i++) { + expectedBytes[i] = parseInt(secretKeyHex.slice(i * 2, i * 2 + 2), 16); + } + + // 5. Check plaintext was removed from main DB + const plaintextGone = await new Promise((resolve, reject) => { + const req = indexedDB.open(storeName); + req.onsuccess = () => { + const db = req.result; + const tx = db.transaction("accountAuth", "readonly"); + const getReq = tx.objectStore("accountAuth").get(pubKeyHex); + getReq.onsuccess = () => { + db.close(); + resolve(getReq.result == null); + }; + getReq.onerror = () => reject(getReq.error); + }; + req.onerror = () => reject(req.error); + }); + + // 6. Check encrypted entry exists in keystore DB + const encryptedExists = await new Promise((resolve, reject) => { + const req = indexedDB.open(`MidenKeystore_${storeName}`); + req.onsuccess = () => { + const db = req.result; + const tx = db.transaction("keys", "readonly"); + const getReq = tx.objectStore("keys").get(pubKeyHex); + getReq.onsuccess = () => { + db.close(); + resolve(getReq.result != null); + }; + getReq.onerror = () => reject(getReq.error); + }; + req.onerror = () => reject(req.error); + }); + + return { + retrieved: retrieved ? Array.from(retrieved) : null, + expected: Array.from(expectedBytes), + plaintextGone, + encryptedExists, + }; + }, storeName); + + expect(result.retrieved).toEqual(result.expected); + expect(result.plaintextGone).toBe(true); + expect(result.encryptedExists).toBe(true); + }); + + test("migration: subsequent getKey reads from encrypted keystore, not main DB", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("migrate-cached"); + + const result = await page.evaluate(async (storeName) => { + // Seed main DB with plaintext key + const pubKeyHex = "cc".repeat(32); + const secretKeyHex = "dd".repeat(64); + + await new Promise((resolve, reject) => { + const req = indexedDB.open(storeName, 1); + req.onupgradeneeded = () => { + const db = req.result; + if (!db.objectStoreNames.contains("accountAuth")) { + db.createObjectStore("accountAuth", { + keyPath: "pubKeyCommitmentHex", + }); + } + }; + req.onsuccess = () => { + const db = req.result; + const tx = db.transaction("accountAuth", "readwrite"); + tx.objectStore("accountAuth").put({ + pubKeyCommitmentHex: pubKeyHex, + secretKeyHex, + }); + tx.oncomplete = () => { + db.close(); + resolve(); + }; + tx.onerror = () => reject(tx.error); + }; + req.onerror = () => reject(req.error); + }); + + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + const keystore = await createPasskeyKeystore(storeName); + + const pubKeyBytes = new Uint8Array(32); + for (let i = 0; i < 32; i++) { + pubKeyBytes[i] = parseInt(pubKeyHex.slice(i * 2, i * 2 + 2), 16); + } + + // First call triggers migration + const first = await keystore.getKey(pubKeyBytes); + + // Second call should read from encrypted keystore (plaintext is gone) + const second = await keystore.getKey(pubKeyBytes); + + return { + first: first ? Array.from(first) : null, + second: second ? Array.from(second) : null, + match: + first != null && + second != null && + Array.from(first).every((b, i) => b === Array.from(second!)[i]), + }; + }, storeName); + + expect(result.first).not.toBeNull(); + expect(result.second).toEqual(result.first); + expect(result.match).toBe(true); + }); + + test("migration: skipped when main DB has no matching key", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("migrate-miss"); + + const result = await page.evaluate(async (storeName) => { + // Create main DB with accountAuth table but no matching entry + await new Promise((resolve, reject) => { + const req = indexedDB.open(storeName, 1); + req.onupgradeneeded = () => { + const db = req.result; + if (!db.objectStoreNames.contains("accountAuth")) { + db.createObjectStore("accountAuth", { + keyPath: "pubKeyCommitmentHex", + }); + } + }; + req.onsuccess = () => { + req.result.close(); + resolve(); + }; + req.onerror = () => reject(req.error); + }); + + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + const keystore = await createPasskeyKeystore(storeName); + + const pubKeyBytes = new Uint8Array(32); + crypto.getRandomValues(pubKeyBytes); + + const retrieved = await keystore.getKey(pubKeyBytes); + return { retrieved }; + }, storeName); + + expect(result.retrieved).toBeUndefined(); + }); + + test("explicit credentialId option reuses existing passkey", async ({ + page, + browserName, + }) => { + test.skip(browserName !== "chromium", "CDP required"); + + const storeName = generateStoreName("explicit-cred"); + + const result = await page.evaluate(async (storeName) => { + const { createPasskeyKeystore } = await import("./passkey-keystore.js"); + + // Register + const ks1 = await createPasskeyKeystore(storeName); + const credentialId = ks1.credentialId; + + // Clear localStorage to prove the explicit option works + localStorage.removeItem(`miden_passkey_credential_${storeName}`); + + // Re-create with explicit credentialId + const differentStore = storeName + "_reuse"; + const ks2 = await createPasskeyKeystore(differentStore, { + credentialId, + }); + + // Insert with ks1, retrieve with ks2 (same PRF → same wrapping key) + const pubKey = new Uint8Array(32); + crypto.getRandomValues(pubKey); + const secretKey = new Uint8Array(48); + crypto.getRandomValues(secretKey); + + await ks1.insertKey(pubKey, secretKey); + + // ks2 has the same wrapping key but different DB + // So we need to use the same DB by using the same storeName + const ks3 = await createPasskeyKeystore(storeName, { credentialId }); + const retrieved = await ks3.getKey(pubKey); + + return { + credentialId1: credentialId, + credentialId2: ks2.credentialId, + original: Array.from(secretKey), + retrieved: retrieved ? Array.from(retrieved) : null, + }; + }, storeName); + + expect(result.credentialId1).toBe(result.credentialId2); + expect(result.retrieved).toEqual(result.original); + }); +}); diff --git a/crates/web-client/yarn.lock b/crates/web-client/yarn.lock index 7ed42a49c1..54514e86f7 100644 --- a/crates/web-client/yarn.lock +++ b/crates/web-client/yarn.lock @@ -93,7 +93,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -195,7 +195,7 @@ dependencies: "@shikijs/types" "3.13.0" -"@shikijs/types@^3.13.0", "@shikijs/types@3.13.0": +"@shikijs/types@3.13.0", "@shikijs/types@^3.13.0": version "3.13.0" resolved "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz" integrity sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw== @@ -463,11 +463,6 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -binaryen@^121.0.0: - version "121.0.0" - resolved "https://registry.npmjs.org/binaryen/-/binaryen-121.0.0.tgz" - integrity sha512-St5LX+CmVdDQMf+DDHWdne7eDK+8tH9TE4Kc+Xk3s5+CzVYIKeJbWuXgsKVbkdLJXGUc2eflFqjThQy555mBag== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" @@ -633,16 +628,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + colorette@^1.1.0: version "1.4.0" resolved "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz" @@ -714,6 +709,13 @@ data-uri-to-buffer@^6.0.2: resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz" integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== +debug@4, debug@^4.1.1, debug@^4.3.4, debug@^4.3.5, debug@^4.3.6: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -721,13 +723,6 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.1, debug@^4.3.4, debug@^4.3.5, debug@^4.3.6, debug@4: - version "4.4.3" - resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - decamelize@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz" @@ -761,7 +756,7 @@ degenerator@^5.0.0: escodegen "^2.1.0" esprima "^4.0.1" -devtools-protocol@*, devtools-protocol@0.0.1330662: +devtools-protocol@0.0.1330662: version "0.0.1330662" resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1330662.tgz" integrity sha512-pzh6YQ8zZfz3iKlCvgzVCu22NdpZ8hNmwU6WnQjNVquh0A9iVosPtNLWDwaWVGyrntQlltPFztTMK5Cg6lfCuw== @@ -1007,16 +1002,16 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - fsevents@2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -1103,18 +1098,7 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.3: - version "8.1.0" - resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@^8.1.0: +glob@^8.0.3, glob@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== @@ -1550,14 +1534,7 @@ minimatch@^5.0.1, minimatch@^5.1.6: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.5: +minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.5" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== @@ -1972,7 +1949,7 @@ rollup-plugin-copy@^3.5.0: globby "10.0.1" is-plain-object "^3.0.0" -rollup@^1.20.0||^2.0.0||^3.0.0||^4.0.0, rollup@^2.14.0||^3.0.0||^4.0.0, rollup@^2.68.0||^3.0.0||^4.0.0, rollup@^2.78.0||^3.0.0||^4.0.0, rollup@^3.27.2: +rollup@^3.27.2: version "3.29.4" resolved "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz" integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== @@ -1986,7 +1963,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@^5.1.0, safe-buffer@5.1.2: +safe-buffer@5.1.2, safe-buffer@^5.1.0: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -2249,7 +2226,7 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tslib@*, tslib@^2.0.1: +tslib@^2.0.1: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -2264,7 +2241,7 @@ typedoc-plugin-markdown@^4.8.1: resolved "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.9.0.tgz" integrity sha512-9Uu4WR9L7ZBgAl60N/h+jqmPxxvnC9nQAlnnO/OujtG2ubjnKTVUFY1XDhcMY+pCqlX3N2HsQM2QTYZIU9tJuw== -typedoc@^0.28.1, typedoc@0.28.x: +typedoc@^0.28.1: version "0.28.13" resolved "https://registry.npmjs.org/typedoc/-/typedoc-0.28.13.tgz" integrity sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w== @@ -2275,7 +2252,7 @@ typedoc@^0.28.1, typedoc@0.28.x: minimatch "^9.0.5" yaml "^2.8.1" -typescript@^5.5.4, typescript@>=2.7, typescript@>=3.7.0, typescript@>=4.9.5, "typescript@5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x": +typescript@^5.5.4: version "5.5.4" resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz" integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== diff --git a/docs/external/src/web-client/passkey-encryption.md b/docs/external/src/web-client/passkey-encryption.md new file mode 100644 index 0000000000..42f2c35d9c --- /dev/null +++ b/docs/external/src/web-client/passkey-encryption.md @@ -0,0 +1,181 @@ +--- +title: Passkey Encryption +sidebar_position: 14 +--- + +# Passkey Encryption (WebAuthn PRF) + +The Miden web SDK supports opt-in passkey-based encryption for secret keys at rest. When enabled, keys stored in IndexedDB are encrypted using AES-256-GCM with a wrapping key derived from the WebAuthn PRF extension (Touch ID, Face ID, Windows Hello). + +Without passkey encryption, secret keys are stored as plaintext in IndexedDB, accessible to any JavaScript running in the same origin (XSS payloads, compromised dependencies, browser extensions). Passkey encryption adds a hardware-backed layer of protection — decrypting keys requires a biometric prompt. + +## Browser Support + +| Browser | Minimum Version | PRF Support | +|---------|----------------|-------------| +| Chrome | 116+ | Yes | +| Edge | 116+ | Yes | +| Safari | 18+ | Yes | +| Firefox | — | Not supported | + +## Quick Start + +### Web SDK (Vanilla JS/TS) + +```typescript +import { MidenClient, isPasskeyPrfSupported } from "@miden-sdk/miden-sdk"; + +// Check browser support first +if (await isPasskeyPrfSupported()) { + const client = await MidenClient.create({ + passkeyEncryption: true, // Triggers biometric prompt + storeName: "my-wallet", // Recommended: explicit store name for migration support + }); + + // All key operations are now transparently encrypted + const wallet = await client.accounts.create(); +} +``` + +### React SDK + +```tsx +import { MidenProvider, isPasskeyPrfSupported } from "@miden-sdk/react"; + +function App() { + return ( + + + + ); +} +``` + +## How It Works + +1. **Registration (once):** `navigator.credentials.create()` registers a passkey with the PRF extension, bound to the current origin. +2. **Authentication (each session):** `navigator.credentials.get()` evaluates the PRF extension, returning a deterministic 32-byte secret from the authenticator's secure enclave. This requires biometric verification (Touch ID, Face ID, PIN). +3. **Key derivation:** The PRF output is fed through HKDF-SHA256 to derive a non-extractable AES-256-GCM wrapping key. +4. **Encrypt on write:** When a new account is created, the secret key is encrypted with the wrapping key and stored in a dedicated `MidenKeystore_*` IndexedDB database. +5. **Decrypt on read:** When a key is needed (e.g., for signing), the ciphertext is read from IndexedDB and decrypted with the wrapping key. The plaintext exists only briefly in WASM memory during signing. + +The wrapping key is held in a JavaScript closure as a non-extractable `CryptoKey` — raw key bytes are never exposed to JavaScript. + +## Feature Detection + +Always check for browser support before offering passkey encryption to users: + +```typescript +import { isPasskeyPrfSupported } from "@miden-sdk/miden-sdk"; +// or +import { isPasskeyPrfSupported } from "@miden-sdk/react"; + +const supported = await isPasskeyPrfSupported(); +if (supported) { + // Safe to enable passkey encryption +} +``` + +## Configuration Options + +### Simple (Register or Reuse) + +```typescript +const client = await MidenClient.create({ + passkeyEncryption: true, +}); +``` + +When `true`, the SDK checks `localStorage` for an existing credential. If found, it authenticates with the existing passkey. If not found, it registers a new passkey. This is the recommended approach for most applications. + +### Explicit Credential + +```typescript +const client = await MidenClient.create({ + passkeyEncryption: { + credentialId: "base64url-encoded-credential-id", + }, +}); +``` + +Pass an explicit credential ID to skip the `localStorage` lookup and authenticate with a specific passkey. Useful for multi-account scenarios or credential management UIs. + +### Full Options + +```typescript +const client = await MidenClient.create({ + passkeyEncryption: { + credentialId: "...", // Existing credential (optional) + rpId: "example.com", // Relying party ID (default: hostname) + rpName: "My Wallet", // Display name during registration + userName: "user@example.com" // User name during registration + }, + storeName: "my-wallet", // Store isolation key +}); +``` + +## Credential Persistence + +- **Credential ID** is stored in `localStorage` under the key `miden_passkey_credential_{storeName}`. This allows the SDK to automatically reuse the passkey in subsequent sessions without re-registration. +- **Encrypted keys** are stored in a separate IndexedDB database (`MidenKeystore_{storeName}`), isolated from the main client database. +- **Wrapping key** exists only in memory (non-extractable `CryptoKey`). It is derived fresh on each `MidenClient.create()` call from the authenticator's PRF output. + +## Migration from Plaintext + +When `getKey` is called and no encrypted entry exists in the keystore database, the SDK attempts to read the plaintext key from the main client database. If found, the key is transparently re-encrypted and the plaintext entry is removed. + +Migration is only available when `storeName` is explicitly provided, since the auto-generated database name (`MidenClientDB_{network_id}`) is not known to JavaScript before WASM initialization. + +## Export/Import Limitations + +The `exportStore()`/`importStore()` flow exports the main WASM store but does **not** include the separate `MidenKeystore_*` database. This means: + +- Exported stores will not include encrypted secret keys. +- Users should perform exports while the passkey-enabled client is active (keys are decrypted in-session). + +## Cross-Device Behavior + +Passkeys sync within the same ecosystem: + +- **Apple** (iCloud Keychain): MacBook, iPhone, iPad share the same PRF output. +- **Google** (Password Manager): Android devices + Chrome share synced passkeys. +- **Windows Hello**: Currently device-bound (no sync). + +Cross-ecosystem (e.g., MacBook to Android) is **not supported**. Users migrating between ecosystems should use the store export/import flow while the client is active. + +## Credential Loss + +If a user loses their passkey (device factory reset without cloud sync, ecosystem switch), encrypted keys are **permanently unrecoverable**. The wrapping key exists only inside the authenticator's secure enclave. + +Mitigations: +1. Recommend enabling cloud sync (iCloud Keychain, Google Password Manager). +2. Users should export their store while the client is active before switching ecosystems. +3. Provide a seed phrase (mnemonic) backup — pass a deterministic `initSeed` when creating the wallet so the same seed regenerates the same keys regardless of the passkey. Note that for **private accounts**, recovering the keys alone is not enough; you must also back up the account data (e.g. via `exportAccountFile`). + +## Security Properties + +| Property | Detail | +|----------|--------| +| Wrapping key | Non-extractable `CryptoKey` — raw bytes never exposed to JS | +| IV (nonce) | Fresh random 12-byte IV per encryption | +| Authentication | AES-GCM 16-byte auth tag detects tampering | +| AAD binding | Ciphertext is bound to its public key commitment, preventing ciphertext swapping | +| Authenticator | Platform-bound (hardware), not roaming/USB keys | +| User verification | Biometric/PIN required on every session | +| Key derivation | HKDF-SHA256 with application-specific salt and info strings | + +## Encrypted Format + +Keys are stored as hex strings in IndexedDB with the `MWEB` envelope format: + +``` +[4B: "MWEB"] [1B: version=0x01] [12B: IV] [NB: AES-GCM ciphertext + 16B auth tag] +``` + +This format is distinct from the native CLI's `MENC` format (which uses Argon2id + ChaCha20-Poly1305). The version byte enables forward-compatible format changes. diff --git a/docs/typedoc/web-client/README.md b/docs/typedoc/web-client/README.md index 0422821f15..03343496f7 100644 --- a/docs/typedoc/web-client/README.md +++ b/docs/typedoc/web-client/README.md @@ -54,6 +54,8 @@ - [NoteOptions](interfaces/NoteOptions.md) - [NotesResource](interfaces/NotesResource.md) - [P2IDEOptions](interfaces/P2IDEOptions.md) +- [PasskeyEncryptionOptions](interfaces/PasskeyEncryptionOptions.md) +- [PasskeyKeystore](interfaces/PasskeyKeystore.md) - [PreviewConsumeOptions](interfaces/PreviewConsumeOptions.md) - [PreviewMintOptions](interfaces/PreviewMintOptions.md) - [PreviewSendOptions](interfaces/PreviewSendOptions.md) @@ -99,4 +101,6 @@ - [buildSwapTag](functions/buildSwapTag.md) - [createP2IDENote](functions/createP2IDENote.md) - [createP2IDNote](functions/createP2IDNote.md) +- [createPasskeyKeystore](functions/createPasskeyKeystore.md) - [getWasmOrThrow](functions/getWasmOrThrow.md) +- [isPasskeyPrfSupported](functions/isPasskeyPrfSupported.md) diff --git a/docs/typedoc/web-client/functions/createPasskeyKeystore.md b/docs/typedoc/web-client/functions/createPasskeyKeystore.md new file mode 100644 index 0000000000..bc45b92a3d --- /dev/null +++ b/docs/typedoc/web-client/functions/createPasskeyKeystore.md @@ -0,0 +1,29 @@ +[**@miden-sdk/miden-sdk**](../README.md) + +*** + +[@miden-sdk/miden-sdk](../README.md) / createPasskeyKeystore + +# Function: createPasskeyKeystore() + +> **createPasskeyKeystore**(`storeName`, `options?`): `Promise`\<[`PasskeyKeystore`](../interfaces/PasskeyKeystore.md)\> + +Creates a passkey-encrypted keystore backed by WebAuthn PRF. + +Registers a new passkey or authenticates with an existing one (biometric prompt), +then returns `getKey`/`insertKey` callbacks that transparently encrypt/decrypt +secret keys using the PRF-derived wrapping key. + +## Parameters + +### storeName + +`string` + +### options? + +[`PasskeyEncryptionOptions`](../interfaces/PasskeyEncryptionOptions.md) + +## Returns + +`Promise`\<[`PasskeyKeystore`](../interfaces/PasskeyKeystore.md)\> diff --git a/docs/typedoc/web-client/functions/isPasskeyPrfSupported.md b/docs/typedoc/web-client/functions/isPasskeyPrfSupported.md new file mode 100644 index 0000000000..dbc53fb43d --- /dev/null +++ b/docs/typedoc/web-client/functions/isPasskeyPrfSupported.md @@ -0,0 +1,18 @@ +[**@miden-sdk/miden-sdk**](../README.md) + +*** + +[@miden-sdk/miden-sdk](../README.md) / isPasskeyPrfSupported + +# Function: isPasskeyPrfSupported() + +> **isPasskeyPrfSupported**(): `Promise`\<`boolean`\> + +Returns `true` if the current browser supports WebAuthn with the PRF extension, +which is required for passkey-based key encryption. + +Supported browsers: Chrome 116+, Safari 18+, Edge 116+. Firefox does NOT support PRF. + +## Returns + +`Promise`\<`boolean`\> diff --git a/docs/typedoc/web-client/interfaces/ClientOptions.md b/docs/typedoc/web-client/interfaces/ClientOptions.md index 8388d5bda5..d256890949 100644 --- a/docs/typedoc/web-client/interfaces/ClientOptions.md +++ b/docs/typedoc/web-client/interfaces/ClientOptions.md @@ -30,9 +30,13 @@ External keystore callbacks. > **insertKey**: [`InsertKeyCallback`](../type-aliases/InsertKeyCallback.md) -#### sign +#### sign? -> **sign**: [`SignCallback`](../type-aliases/SignCallback.md) +> `optional` **sign**: [`SignCallback`](../type-aliases/SignCallback.md) + +Optional signing callback. When omitted, the Rust/WASM side calls `getKey` +to retrieve the secret key and signs locally. Only provide this if you need +signing to happen outside of WASM (e.g., in a remote HSM). *** @@ -44,6 +48,21 @@ Note transport URL (optional). *** +### passkeyEncryption? + +> `optional` **passkeyEncryption**: `boolean` \| [`PasskeyEncryptionOptions`](PasskeyEncryptionOptions.md) + +Opt-in passkey encryption for keys at rest. Pass `true` for defaults +or a `PasskeyEncryptionOptions` object to reuse an existing credential. + +When `true`, checks localStorage for an existing credential and reuses it +if found; otherwise registers a new passkey (triggering a biometric prompt). + +Requires Chrome 116+, Safari 18+, or Edge 116+. Firefox does NOT support PRF. +Mutually exclusive with `keystore` — if both are provided, `keystore` takes precedence. + +*** + ### proverUrl? > `optional` **proverUrl**: `string` diff --git a/docs/typedoc/web-client/interfaces/PasskeyEncryptionOptions.md b/docs/typedoc/web-client/interfaces/PasskeyEncryptionOptions.md new file mode 100644 index 0000000000..0ca511c7f4 --- /dev/null +++ b/docs/typedoc/web-client/interfaces/PasskeyEncryptionOptions.md @@ -0,0 +1,39 @@ +[**@miden-sdk/miden-sdk**](../README.md) + +*** + +[@miden-sdk/miden-sdk](../README.md) / PasskeyEncryptionOptions + +# Interface: PasskeyEncryptionOptions + +## Properties + +### credentialId? + +> `optional` **credentialId**: `string` + +Existing credential ID (base64url). Omit to register a new passkey. + +*** + +### rpId? + +> `optional` **rpId**: `string` + +WebAuthn relying party ID. Defaults to current hostname. + +*** + +### rpName? + +> `optional` **rpName**: `string` + +Relying party display name. Defaults to "Miden Client". + +*** + +### userName? + +> `optional` **userName**: `string` + +User display name for the passkey. Defaults to "Miden Wallet User". diff --git a/docs/typedoc/web-client/interfaces/PasskeyKeystore.md b/docs/typedoc/web-client/interfaces/PasskeyKeystore.md new file mode 100644 index 0000000000..e841125c7e --- /dev/null +++ b/docs/typedoc/web-client/interfaces/PasskeyKeystore.md @@ -0,0 +1,33 @@ +[**@miden-sdk/miden-sdk**](../README.md) + +*** + +[@miden-sdk/miden-sdk](../README.md) / PasskeyKeystore + +# Interface: PasskeyKeystore + +Result of `createPasskeyKeystore()`. + +## Properties + +### credentialId + +> **credentialId**: `string` + +The credential ID (base64url) of the passkey used for this keystore. + +*** + +### getKey + +> **getKey**: [`GetKeyCallback`](../type-aliases/GetKeyCallback.md) + +Decrypts and returns the secret key for a given pub key commitment. + +*** + +### insertKey + +> **insertKey**: [`InsertKeyCallback`](../type-aliases/InsertKeyCallback.md) + +Encrypts and stores a secret key for a given pub key commitment. diff --git a/eslint.config.js b/eslint.config.js index 87e0236b07..a7d2630141 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -10,6 +10,7 @@ module.exports = [ "docs/**/*", "crates/idxdb-store/src/**", "packages/react-sdk/**", + "packages/vite-plugin/**", ], }, { diff --git a/packages/react-sdk/CLAUDE.md b/packages/react-sdk/CLAUDE.md index 08b8e15ac5..1ac4f3ad1a 100644 --- a/packages/react-sdk/CLAUDE.md +++ b/packages/react-sdk/CLAUDE.md @@ -31,6 +31,8 @@ function App() { prover: "testnet", // "local" | "devnet" | "testnet" | custom URL autoSyncInterval: 15000, // ms, set to 0 to disable noteTransportUrl: "...", // optional: for private note delivery + passkeyEncryption: true, // optional: encrypt keys with WebAuthn PRF (biometric) + storeName: "my-wallet", // recommended when using passkeyEncryption }} loadingComponent={} // shown during WASM init errorComponent={} // shown on init failure @@ -43,6 +45,29 @@ function App() { | `testnet` | Pre-production testing | | `localhost` | Local node at `http://localhost:57291` | +## Passkey Encryption + +Encrypt keys at rest using WebAuthn PRF (Touch ID / Face ID / Windows Hello). Requires Chrome 116+, Safari 18+, or Edge 116+. + +```tsx +import { MidenProvider, isPasskeyPrfSupported } from "@miden-sdk/react"; + +// Check support +const supported = await isPasskeyPrfSupported(); + +// Enable in provider + +``` + +- Biometric prompt fires once per `MidenProvider` mount +- Keys encrypted with AES-256-GCM, wrapping key never exposed to JS +- Ignored when a `SignerContext` is active (external signers handle their own keys) +- Credential loss = key loss (recommend iCloud Keychain / Google Password Manager) + ## Reading Data (Query Hooks) All query hooks return `{ data, isLoading, error, refetch }`. diff --git a/packages/react-sdk/README.md b/packages/react-sdk/README.md index 7653fced52..9589f55334 100644 --- a/packages/react-sdk/README.md +++ b/packages/react-sdk/README.md @@ -113,6 +113,82 @@ function App() { } ``` +## Passkey Encryption + +The SDK supports opt-in passkey-based encryption for secret keys at rest. When enabled, keys stored in IndexedDB are encrypted using AES-256-GCM with a wrapping key derived from the WebAuthn PRF extension (Touch ID, Face ID, Windows Hello). Decrypting keys requires a biometric prompt each session. + +**Browser support:** Chrome 116+, Safari 18+, Edge 116+. Firefox does not support PRF. + +### Basic Usage + +```tsx +import { MidenProvider, isPasskeyPrfSupported } from '@miden-sdk/react'; + +function App() { + return ( + + + + ); +} +``` + +When `passkeyEncryption: true`, the provider checks `localStorage` for an existing passkey credential. If found, it authenticates with the existing passkey. If not found, it registers a new passkey. Both trigger a biometric prompt. + +### Feature Detection + +Always check for support before enabling passkey encryption: + +```tsx +import { isPasskeyPrfSupported } from '@miden-sdk/react'; + +function PasskeyToggle({ onToggle }) { + const [supported, setSupported] = useState(false); + + useEffect(() => { + isPasskeyPrfSupported().then(setSupported); + }, []); + + if (!supported) return null; + + return ( + + ); +} +``` + +### Advanced Options + +```tsx + +``` + +### Important Notes + +- **Credential loss is permanent:** If a user loses their passkey (device factory reset without cloud sync), encrypted keys are unrecoverable. Recommend enabling iCloud Keychain or Google Password Manager. +- **Not compatible with external signers:** When a `SignerContext` is active (Para, Turnkey, etc.), `passkeyEncryption` is ignored — the signer handles key management. +- **Cross-ecosystem:** Passkeys sync within Apple or Google ecosystems but not across them. Users migrating should export their store first. +- **Export/import:** `exportStore()`/`importStore()` does not include the encrypted keystore. Export while the client is active. + ## Hooks Reference ### Core Hooks diff --git a/packages/react-sdk/examples/wallet/README.md b/packages/react-sdk/examples/wallet/README.md index 402fafe650..50660e160d 100644 --- a/packages/react-sdk/examples/wallet/README.md +++ b/packages/react-sdk/examples/wallet/README.md @@ -12,3 +12,22 @@ cd examples/wallet yarn install yarn dev ``` + +## Key Recovery Warning + +This example uses passkey encryption (`passkeyEncryption: true`) to protect account +keys at rest. The passkey (Touch ID / Face ID / Windows Hello) is the **sole** mechanism +guarding access to the encrypted keys in IndexedDB. If the passkey is lost or the +credential is deleted, the keys become **permanently unrecoverable**. + +A production wallet should implement one or more recovery strategies: + +- **Seed phrase backup** — provide a deterministic `initSeed` when creating the wallet + and derive a mnemonic the user can write down. The same seed regenerates the same keys + regardless of the passkey. Note: for **private accounts** (where account state is stored + only locally, not on-chain), recovering the keys alone is not enough — you must also + back up the account data (e.g. via `exportAccountFile`). +- **Account file export** — use `exportAccountFile()` while the passkey session is active + to create a portable backup containing both the account state and secret keys. +- **External signer** — delegate key management to an external service (e.g. Turnkey, + Para) that provides its own recovery flow. diff --git a/packages/react-sdk/examples/wallet/package.json b/packages/react-sdk/examples/wallet/package.json index 1748f013b2..16951997e5 100644 --- a/packages/react-sdk/examples/wallet/package.json +++ b/packages/react-sdk/examples/wallet/package.json @@ -9,12 +9,13 @@ "preview": "vite preview" }, "dependencies": { - "@miden-sdk/miden-sdk": "^0.13.0", + "@miden-sdk/miden-sdk": "file:../../../../crates/web-client", "@miden-sdk/react": "file:../..", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@miden-sdk/vite-plugin": "file:../../../vite-plugin", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", diff --git a/packages/react-sdk/examples/wallet/src/main.tsx b/packages/react-sdk/examples/wallet/src/main.tsx index efee011513..70b2824a3f 100644 --- a/packages/react-sdk/examples/wallet/src/main.tsx +++ b/packages/react-sdk/examples/wallet/src/main.tsx @@ -10,6 +10,8 @@ ReactDOM.createRoot(document.getElementById("root")!).render( config={{ rpcUrl: "devnet", prover: "devnet", + passkeyEncryption: true, + storeName: "wallet-example", }} > diff --git a/packages/react-sdk/examples/wallet/vite.config.ts b/packages/react-sdk/examples/wallet/vite.config.ts index 99fc821dee..53f9b15d33 100644 --- a/packages/react-sdk/examples/wallet/vite.config.ts +++ b/packages/react-sdk/examples/wallet/vite.config.ts @@ -1,11 +1,9 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import { midenVitePlugin } from "@miden-sdk/vite-plugin"; export default defineConfig({ - plugins: [react()], - optimizeDeps: { - exclude: ["@miden-sdk/miden-sdk"], - }, + plugins: [react(), midenVitePlugin({ rpcProxyTarget: false })], resolve: { dedupe: ["react", "react-dom", "react/jsx-runtime"], }, diff --git a/packages/react-sdk/examples/wallet/yarn.lock b/packages/react-sdk/examples/wallet/yarn.lock index bb24978cfc..a8bd4fb574 100644 --- a/packages/react-sdk/examples/wallet/yarn.lock +++ b/packages/react-sdk/examples/wallet/yarn.lock @@ -2,34 +2,34 @@ # yarn lockfile v1 -"@babel/code-frame@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" - integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== +"@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== dependencies: "@babel/helper-validator-identifier" "^7.28.5" js-tokens "^4.0.0" picocolors "^1.1.1" "@babel/compat-data@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.6.tgz#103f466803fa0f059e82ccac271475470570d74c" - integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== "@babel/core@^7.28.0": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.6.tgz#531bf883a1126e53501ba46eb3bb414047af507f" - integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== dependencies: - "@babel/code-frame" "^7.28.6" - "@babel/generator" "^7.28.6" + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" "@babel/helper-compilation-targets" "^7.28.6" "@babel/helper-module-transforms" "^7.28.6" "@babel/helpers" "^7.28.6" - "@babel/parser" "^7.28.6" + "@babel/parser" "^7.29.0" "@babel/template" "^7.28.6" - "@babel/traverse" "^7.28.6" - "@babel/types" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" @@ -37,13 +37,13 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.6.tgz#48dcc65d98fcc8626a48f72b62e263d25fc3c3f1" - integrity sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw== +"@babel/generator@^7.29.0": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== dependencies: - "@babel/parser" "^7.28.6" - "@babel/types" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" "@jridgewell/gen-mapping" "^0.3.12" "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" @@ -109,12 +109,12 @@ "@babel/template" "^7.28.6" "@babel/types" "^7.28.6" -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" - integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" + integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== dependencies: - "@babel/types" "^7.28.6" + "@babel/types" "^7.29.0" "@babel/plugin-transform-react-jsx-self@^7.27.1": version "7.27.1" @@ -139,23 +139,23 @@ "@babel/parser" "^7.28.6" "@babel/types" "^7.28.6" -"@babel/traverse@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.6.tgz#871ddc79a80599a5030c53b1cc48cbe3a5583c2e" - integrity sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg== +"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== dependencies: - "@babel/code-frame" "^7.28.6" - "@babel/generator" "^7.28.6" + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.6" + "@babel/parser" "^7.29.0" "@babel/template" "^7.28.6" - "@babel/types" "^7.28.6" + "@babel/types" "^7.29.0" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" - integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== dependencies: "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" @@ -275,29 +275,10 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== -"@isaacs/balanced-match@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" - integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== - -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== - dependencies: - "@isaacs/balanced-match" "^4.0.1" - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@isaacs/cliui@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-9.0.0.tgz#4d0a3f127058043bf2e7ee169eaf30ed901302f3" + integrity sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg== "@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": version "0.3.13" @@ -333,20 +314,21 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@miden-sdk/miden-sdk@^0.13.0": +"@miden-sdk/miden-sdk@file:../../../../crates/web-client": version "0.13.0" - resolved "https://registry.yarnpkg.com/@miden-sdk/miden-sdk/-/miden-sdk-0.13.0.tgz#df7f639f90931d279761a62bb513374366d755e4" - integrity sha512-N0qUCZW9Dvk3Oqj37IrGmm0b0v3Nq5qHsX3BtQIzZIwDXKXKPBxy/0lO40oCwDtwI8AfriZQyMLbJR81Fo4Vpg== dependencies: "@rollup/plugin-typescript" "^12.3.0" dexie "^4.0.1" glob "^11.0.0" "@miden-sdk/react@file:../..": - version "0.13.0" + version "0.13.2" dependencies: zustand "^5.0.0" +"@miden-sdk/vite-plugin@file:../../../vite-plugin": + version "0.13.4" + "@rolldown/pluginutils@1.0.0-beta.27": version "1.0.0-beta.27" resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz#47d2bf4cef6d470b22f5831b420f8964e0bf755f" @@ -369,130 +351,130 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-android-arm-eabi@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz#067cfcd81f1c1bfd92aefe3ad5ef1523549d5052" - integrity sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw== - -"@rollup/rollup-android-arm64@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz#85e39a44034d7d4e4fee2a1616f0bddb85a80517" - integrity sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q== - -"@rollup/rollup-darwin-arm64@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz#17d92fe98f2cc277b91101eb1528b7c0b6c00c54" - integrity sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w== - -"@rollup/rollup-darwin-x64@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz#89ae6c66b1451609bd1f297da9384463f628437d" - integrity sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g== - -"@rollup/rollup-freebsd-arm64@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz#cdbdb9947b26e76c188a31238c10639347413628" - integrity sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ== - -"@rollup/rollup-freebsd-x64@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz#9b1458d07b6e040be16ee36d308a2c9520f7f7cc" - integrity sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg== - -"@rollup/rollup-linux-arm-gnueabihf@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz#1d50ded7c965d5f125f5832c971ad5b287befef7" - integrity sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A== - -"@rollup/rollup-linux-arm-musleabihf@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz#53597e319b7e65990d3bc2a5048097384814c179" - integrity sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw== - -"@rollup/rollup-linux-arm64-gnu@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz#597002909dec198ca4bdccb25f043d32db3d6283" - integrity sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ== - -"@rollup/rollup-linux-arm64-musl@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz#286f0e0f799545ce288bdc5a7c777261fcba3d54" - integrity sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA== - -"@rollup/rollup-linux-loong64-gnu@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz#1fab07fa1a4f8d3697735b996517f1bae0ba101b" - integrity sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg== - -"@rollup/rollup-linux-loong64-musl@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz#efc2cb143d6c067f95205482afb177f78ed9ea3d" - integrity sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA== - -"@rollup/rollup-linux-ppc64-gnu@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz#e8de8bd3463f96b92b7dfb7f151fd80ffe8a937c" - integrity sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw== - -"@rollup/rollup-linux-ppc64-musl@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz#8c508fe28a239da83b3a9da75bcf093186e064b4" - integrity sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg== - -"@rollup/rollup-linux-riscv64-gnu@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz#ff6d51976e0830732880770a9e18553136b8d92b" - integrity sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew== - -"@rollup/rollup-linux-riscv64-musl@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz#325fb35eefc7e81d75478318f0deee1e4a111493" - integrity sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ== - -"@rollup/rollup-linux-s390x-gnu@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz#37410fabb5d3ba4ad34abcfbe9ba9b6288413f30" - integrity sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ== - -"@rollup/rollup-linux-x64-gnu@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz#8ef907a53b2042068fc03fcc6a641e2b02276eca" - integrity sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw== - -"@rollup/rollup-linux-x64-musl@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz#61b9ba09ea219e0174b3f35a6ad2afc94bdd5662" - integrity sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA== - -"@rollup/rollup-openbsd-x64@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz#fc4e54133134c1787d0b016ffdd5aeb22a5effd3" - integrity sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA== - -"@rollup/rollup-openharmony-arm64@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz#959ae225b1eeea0cc5b7c9f88e4834330fb6cd09" - integrity sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ== - -"@rollup/rollup-win32-arm64-msvc@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz#842acd38869fa1cbdbc240c76c67a86f93444c27" - integrity sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing== - -"@rollup/rollup-win32-ia32-msvc@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz#7ab654def4042df44cb29f8ed9d5044e850c66d5" - integrity sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg== - -"@rollup/rollup-win32-x64-gnu@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz#7426cdec1b01d2382ffd5cda83cbdd1c8efb3ca6" - integrity sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ== - -"@rollup/rollup-win32-x64-msvc@4.56.0": - version "4.56.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz#9eec0212732a432c71bde0350bc40b673d15b2db" - integrity sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g== +"@rollup/rollup-android-arm-eabi@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz#a6742c74c7d9d6d604ef8a48f99326b4ecda3d82" + integrity sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg== + +"@rollup/rollup-android-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz#97247be098de4df0c11971089fd2edf80a5da8cf" + integrity sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q== + +"@rollup/rollup-darwin-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz#674852cf14cf11b8056e0b1a2f4e872b523576cf" + integrity sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg== + +"@rollup/rollup-darwin-x64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz#36dfd7ed0aaf4d9d89d9ef983af72632455b0246" + integrity sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w== + +"@rollup/rollup-freebsd-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz#2f87c2074b4220260fdb52a9996246edfc633c22" + integrity sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA== + +"@rollup/rollup-freebsd-x64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz#9b5a26522a38a95dc06616d1939d4d9a76937803" + integrity sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg== + +"@rollup/rollup-linux-arm-gnueabihf@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz#86aa4859385a8734235b5e40a48e52d770758c3a" + integrity sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw== + +"@rollup/rollup-linux-arm-musleabihf@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz#cbe70e56e6ece8dac83eb773b624fc9e5a460976" + integrity sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA== + +"@rollup/rollup-linux-arm64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz#d14992a2e653bc3263d284bc6579b7a2890e1c45" + integrity sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA== + +"@rollup/rollup-linux-arm64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz#2fdd1ddc434ea90aeaa0851d2044789b4d07f6da" + integrity sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA== + +"@rollup/rollup-linux-loong64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz#8a181e6f89f969f21666a743cd411416c80099e7" + integrity sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg== + +"@rollup/rollup-linux-loong64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz#904125af2babc395f8061daa27b5af1f4e3f2f78" + integrity sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q== + +"@rollup/rollup-linux-ppc64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz#a57970ac6864c9a3447411a658224bdcf948be22" + integrity sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA== + +"@rollup/rollup-linux-ppc64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz#bb84de5b26870567a4267666e08891e80bb56a63" + integrity sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA== + +"@rollup/rollup-linux-riscv64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz#72d00d2c7fb375ce3564e759db33f17a35bffab9" + integrity sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg== + +"@rollup/rollup-linux-riscv64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz#4c166ef58e718f9245bd31873384ba15a5c1a883" + integrity sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg== + +"@rollup/rollup-linux-s390x-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz#bb5025cde9a61db478c2ca7215808ad3bce73a09" + integrity sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w== + +"@rollup/rollup-linux-x64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz#9b66b1f9cd95c6624c788f021c756269ffed1552" + integrity sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg== + +"@rollup/rollup-linux-x64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz#b007ca255dc7166017d57d7d2451963f0bd23fd9" + integrity sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg== + +"@rollup/rollup-openbsd-x64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz#e8b357b2d1aa2c8d76a98f5f0d889eabe93f4ef9" + integrity sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ== + +"@rollup/rollup-openharmony-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz#96c2e3f4aacd3d921981329831ff8dde492204dc" + integrity sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA== + +"@rollup/rollup-win32-arm64-msvc@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz#2d865149d706d938df8b4b8f117e69a77646d581" + integrity sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A== + +"@rollup/rollup-win32-ia32-msvc@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz#abe1593be0fa92325e9971c8da429c5e05b92c36" + integrity sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA== + +"@rollup/rollup-win32-x64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz#c4af3e9518c9a5cd4b1c163dc81d0ad4d82e7eab" + integrity sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA== + +"@rollup/rollup-win32-x64-msvc@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c" + integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== "@types/babel__core@^7.20.5": version "7.20.5" @@ -543,9 +525,9 @@ integrity sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ== "@types/react@^18.2.0": - version "18.3.27" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.27.tgz#74a3b590ea183983dc65a474dc17553ae1415c34" - integrity sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w== + version "18.3.28" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.28.tgz#0a85b1a7243b4258d9f626f43797ba18eb5f8781" + integrity sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw== dependencies: "@types/prop-types" "*" csstype "^3.2.2" @@ -562,32 +544,22 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.17.0" -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== -ansi-regex@^6.0.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" - integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== +baseline-browser-mapping@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9" + integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA== -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== +brace-expansion@^5.0.2: + version "5.0.3" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.3.tgz#6a9c6c268f85b53959ec527aeafe0f7300258eef" + integrity sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA== dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.1.0: - version "6.2.3" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" - integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== - -baseline-browser-mapping@^2.9.0: - version "2.9.18" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz#c8281693035a9261b10d662a5379650a6c2d1ff7" - integrity sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA== + balanced-match "^4.0.2" browserslist@^4.24.0: version "4.28.1" @@ -601,21 +573,9 @@ browserslist@^4.24.0: update-browserslist-db "^1.2.0" caniuse-lite@^1.0.30001759: - version "1.0.30001766" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz#b6f6b55cb25a2d888d9393104d14751c6a7d6f7a" - integrity sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + version "1.0.30001774" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz#0e576b6f374063abcd499d202b9ba1301be29b70" + integrity sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA== convert-source-map@^2.0.0: version "2.0.0" @@ -644,29 +604,14 @@ debug@^4.1.0, debug@^4.3.1: ms "^2.1.3" dexie@^4.0.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/dexie/-/dexie-4.2.1.tgz#70d111ae8d2dabf53f424fca79f6f918c407e6db" - integrity sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg== - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + version "4.3.0" + resolved "https://registry.yarnpkg.com/dexie/-/dexie-4.3.0.tgz#5b6b9e3ed4e48ba1c36442e6a7d07b86335e2df5" + integrity sha512-5EeoQpJvMKHe6zWt/FSIIuRa3CWlZeIl6zKXt+Lz7BU6RoRRLgX9dZEynRfXrkLcldKYCBiz7xekTEylnie1Ug== electron-to-chromium@^1.5.263: - version "1.5.278" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz#807a5e321f012a41bfd64e653f35993c9af95493" - integrity sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + version "1.5.302" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz#032a5802b31f7119269959c69fe2015d8dad5edb" + integrity sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg== esbuild@^0.21.3: version "0.21.5" @@ -756,22 +701,17 @@ is-core-module@^2.16.1: dependencies: hasown "^2.0.2" -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== jackspeak@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.1.1.tgz#96876030f450502047fc7e8c7fcf8ce8124e43ae" - integrity sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ== + version "4.2.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.2.3.tgz#27ef80f33b93412037c3bea4f8eddf80e1931483" + integrity sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg== dependencies: - "@isaacs/cliui" "^8.0.2" + "@isaacs/cliui" "^9.0.0" "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -796,9 +736,9 @@ loose-envify@^1.1.0: js-tokens "^3.0.0 || ^4.0.0" lru-cache@^11.0.0: - version "11.2.4" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.4.tgz#ecb523ebb0e6f4d837c807ad1abaea8e0619770d" - integrity sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg== + version "11.2.6" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.6.tgz#356bf8a29e88a7a2945507b31f6429a65a192c58" + integrity sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ== lru-cache@^5.1.1: version "5.1.1" @@ -808,16 +748,16 @@ lru-cache@^5.1.1: yallist "^3.0.2" minimatch@^10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" - integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== + version "10.2.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" + integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== dependencies: - "@isaacs/brace-expansion" "^5.0.0" + brace-expansion "^5.0.2" minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + version "7.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== ms@^2.1.3: version "2.1.3" @@ -850,9 +790,9 @@ path-parse@^1.0.7: integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-scurry@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.1.tgz#4b6572376cfd8b811fca9cd1f5c24b3cbac0fe10" - integrity sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA== + version "2.0.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" + integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== dependencies: lru-cache "^11.0.0" minipass "^7.1.2" @@ -906,37 +846,37 @@ resolve@^1.22.1: supports-preserve-symlinks-flag "^1.0.0" rollup@^4.20.0: - version "4.56.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.56.0.tgz#65959d13cfbd7e48b8868c05165b1738f0143862" - integrity sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg== + version "4.59.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.59.0.tgz#cf74edac17c1486f562d728a4d923a694abdf06f" + integrity sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg== dependencies: "@types/estree" "1.0.8" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.56.0" - "@rollup/rollup-android-arm64" "4.56.0" - "@rollup/rollup-darwin-arm64" "4.56.0" - "@rollup/rollup-darwin-x64" "4.56.0" - "@rollup/rollup-freebsd-arm64" "4.56.0" - "@rollup/rollup-freebsd-x64" "4.56.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.56.0" - "@rollup/rollup-linux-arm-musleabihf" "4.56.0" - "@rollup/rollup-linux-arm64-gnu" "4.56.0" - "@rollup/rollup-linux-arm64-musl" "4.56.0" - "@rollup/rollup-linux-loong64-gnu" "4.56.0" - "@rollup/rollup-linux-loong64-musl" "4.56.0" - "@rollup/rollup-linux-ppc64-gnu" "4.56.0" - "@rollup/rollup-linux-ppc64-musl" "4.56.0" - "@rollup/rollup-linux-riscv64-gnu" "4.56.0" - "@rollup/rollup-linux-riscv64-musl" "4.56.0" - "@rollup/rollup-linux-s390x-gnu" "4.56.0" - "@rollup/rollup-linux-x64-gnu" "4.56.0" - "@rollup/rollup-linux-x64-musl" "4.56.0" - "@rollup/rollup-openbsd-x64" "4.56.0" - "@rollup/rollup-openharmony-arm64" "4.56.0" - "@rollup/rollup-win32-arm64-msvc" "4.56.0" - "@rollup/rollup-win32-ia32-msvc" "4.56.0" - "@rollup/rollup-win32-x64-gnu" "4.56.0" - "@rollup/rollup-win32-x64-msvc" "4.56.0" + "@rollup/rollup-android-arm-eabi" "4.59.0" + "@rollup/rollup-android-arm64" "4.59.0" + "@rollup/rollup-darwin-arm64" "4.59.0" + "@rollup/rollup-darwin-x64" "4.59.0" + "@rollup/rollup-freebsd-arm64" "4.59.0" + "@rollup/rollup-freebsd-x64" "4.59.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.59.0" + "@rollup/rollup-linux-arm-musleabihf" "4.59.0" + "@rollup/rollup-linux-arm64-gnu" "4.59.0" + "@rollup/rollup-linux-arm64-musl" "4.59.0" + "@rollup/rollup-linux-loong64-gnu" "4.59.0" + "@rollup/rollup-linux-loong64-musl" "4.59.0" + "@rollup/rollup-linux-ppc64-gnu" "4.59.0" + "@rollup/rollup-linux-ppc64-musl" "4.59.0" + "@rollup/rollup-linux-riscv64-gnu" "4.59.0" + "@rollup/rollup-linux-riscv64-musl" "4.59.0" + "@rollup/rollup-linux-s390x-gnu" "4.59.0" + "@rollup/rollup-linux-x64-gnu" "4.59.0" + "@rollup/rollup-linux-x64-musl" "4.59.0" + "@rollup/rollup-openbsd-x64" "4.59.0" + "@rollup/rollup-openharmony-arm64" "4.59.0" + "@rollup/rollup-win32-arm64-msvc" "4.59.0" + "@rollup/rollup-win32-ia32-msvc" "4.59.0" + "@rollup/rollup-win32-x64-gnu" "4.59.0" + "@rollup/rollup-win32-x64-msvc" "4.59.0" fsevents "~2.3.2" scheduler@^0.23.2: @@ -973,54 +913,6 @@ source-map-js@^1.2.1: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" - integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== - dependencies: - ansi-regex "^6.0.1" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -1057,30 +949,12 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== zustand@^5.0.0: - version "5.0.10" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.10.tgz#4db510c0c4c25a5f1ae43227b307ddf1641a3210" - integrity sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg== + version "5.0.11" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.11.tgz#99f912e590de1ca9ce6c6d1cab6cdb1f034ab494" + integrity sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg== diff --git a/packages/react-sdk/src/context/MidenProvider.tsx b/packages/react-sdk/src/context/MidenProvider.tsx index a0a5bc554a..e4d3e281c2 100644 --- a/packages/react-sdk/src/context/MidenProvider.tsx +++ b/packages/react-sdk/src/context/MidenProvider.tsx @@ -161,7 +161,7 @@ export function MidenProvider({ setConfig(resolvedConfig); try { - let webClient: WebClient; + let webClient: WebClient | undefined; let didSignerInit = false; if (signerContext && signerContext.isConnected) { @@ -189,15 +189,50 @@ export function MidenProvider({ if (cancelled) return; setSignerAccountId(accountId); didSignerInit = true; - } else { - // No signer provider - standard local keystore (existing behavior) + } else if (resolvedConfig.passkeyEncryption) { + // Passkey encryption mode — local keystore with encrypted keys. + // Fall back to standard (unencrypted) mode if the browser doesn't + // support WebAuthn PRF (e.g. Firefox, older browsers). + const { createPasskeyKeystore, isPasskeyPrfSupported } = + await import("@miden-sdk/miden-sdk"); + const supported = await isPasskeyPrfSupported(); + + if (supported) { + const passkeyOpts = + typeof resolvedConfig.passkeyEncryption === "object" + ? resolvedConfig.passkeyEncryption + : {}; + const storeName = resolvedConfig.storeName || "default"; + const keystore = await createPasskeyKeystore( + storeName, + passkeyOpts + ); + if (cancelled) return; + + webClient = await WebClient.createClientWithExternalKeystore( + resolvedConfig.rpcUrl, + resolvedConfig.noteTransportUrl, + resolvedConfig.seed, + storeName, + keystore.getKey, + keystore.insertKey, + undefined // sign — Rust signs locally using getKey + ); + if (cancelled) return; + } + // else: fall through to standard createClient below + } + + if (!webClient) { + // Standard local keystore (no signer, no passkey or unsupported) const seed = resolvedConfig.seed as Parameters< typeof WebClient.createClient >[2]; webClient = await WebClient.createClient( resolvedConfig.rpcUrl, resolvedConfig.noteTransportUrl, - seed + seed, + resolvedConfig.storeName ); if (cancelled) return; } @@ -248,6 +283,10 @@ export function MidenProvider({ initClient(); return () => { cancelled = true; + // Reset so StrictMode's second invocation can re-trigger init + if (!signerContext) { + isInitializedRef.current = false; + } }; }, [ runExclusive, diff --git a/packages/react-sdk/src/index.ts b/packages/react-sdk/src/index.ts index c0f4c72f44..705ba3368e 100644 --- a/packages/react-sdk/src/index.ts +++ b/packages/react-sdk/src/index.ts @@ -35,9 +35,13 @@ export { useConsume } from "./hooks/useConsume"; export { useSwap } from "./hooks/useSwap"; export { useTransaction } from "./hooks/useTransaction"; +// Passkey utilities (re-exported from @miden-sdk/miden-sdk) +export { isPasskeyPrfSupported } from "@miden-sdk/miden-sdk"; + // Types export type { MidenConfig, + PasskeyEncryptionOptions, RpcUrlConfig, ProverConfig, ProverUrls, diff --git a/packages/react-sdk/src/types/index.ts b/packages/react-sdk/src/types/index.ts index e94915a3e4..12dd03c102 100644 --- a/packages/react-sdk/src/types/index.ts +++ b/packages/react-sdk/src/types/index.ts @@ -61,6 +61,18 @@ export type ProverUrls = { testnet?: string; }; +/** Options for passkey-based key encryption (WebAuthn PRF). */ +export interface PasskeyEncryptionOptions { + /** Existing credential ID (base64url). Omit to register a new passkey. */ + credentialId?: string; + /** WebAuthn relying party ID. Defaults to current hostname. */ + rpId?: string; + /** Relying party display name. Defaults to "Miden Client". */ + rpName?: string; + /** User display name for the passkey. Defaults to "Miden Wallet User". */ + userName?: string; +} + // Provider configuration export interface MidenConfig { /** RPC node URL or network name (devnet/testnet/localhost). Defaults to testnet. */ @@ -77,6 +89,20 @@ export interface MidenConfig { proverUrls?: ProverUrls; /** Default timeout for remote prover requests in milliseconds. */ proverTimeoutMs?: number | bigint; + /** Store isolation key. Recommended when using passkeyEncryption (defaults to "default"). */ + storeName?: string; + /** + * Opt-in passkey encryption for keys at rest. Pass `true` for defaults + * or a `PasskeyEncryptionOptions` object to reuse an existing credential. + * + * When `true`, checks localStorage for an existing credential and reuses it + * if found; otherwise registers a new passkey (triggering a biometric prompt). + * + * Requires Chrome 116+, Safari 18+, or Edge 116+. + * Not compatible with external signer mode — if a SignerContext is active, + * this option is ignored. + */ + passkeyEncryption?: boolean | PasskeyEncryptionOptions; } // Provider state diff --git a/packages/vite-plugin/README.md b/packages/vite-plugin/README.md new file mode 100644 index 0000000000..cf1416cfb1 --- /dev/null +++ b/packages/vite-plugin/README.md @@ -0,0 +1,81 @@ +# @miden-sdk/vite-plugin + +Vite plugin for Miden dApps. Automates WASM deduplication, cross-origin isolation headers, and gRPC-web proxy configuration. + +## Installation + +```bash +npm install @miden-sdk/vite-plugin --save-dev +# or +yarn add @miden-sdk/vite-plugin --dev +``` + +## Usage + +```typescript +// vite.config.ts +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { midenVitePlugin } from "@miden-sdk/vite-plugin"; + +export default defineConfig({ + plugins: [ + midenVitePlugin(), // zero-config: all defaults + react(), + ], +}); +``` + +### With Options + +```typescript +midenVitePlugin({ + rpcProxyTarget: "https://rpc.testnet.miden.io", // default + rpcProxyPath: "/rpc.Api", // default + crossOriginIsolation: true, // default + wasmPackages: ["@miden-sdk/miden-sdk"], // default +}); +``` + +## What It Does + +| Config | Purpose | +|--------|---------| +| `resolve.alias` | Force single copy of WASM module (avoids class identity issues) | +| `resolve.dedupe` | Vite deduplication hint | +| `resolve.preserveSymlinks` | Monorepo/symlink support | +| `optimizeDeps.exclude` | Don't pre-bundle WASM packages | +| `server.headers` (COOP/COEP) | SharedArrayBuffer for WASM workers | +| `server.proxy` | gRPC-web CORS bypass in dev | +| `build.target: "esnext"` | Top-level await for WASM | +| `worker.format: "es"` | ES module workers for WASM | + +## Options + +### `wasmPackages` +- **Type:** `string[]` +- **Default:** `["@miden-sdk/miden-sdk"]` +- Packages to deduplicate and exclude from pre-bundling. + +### `crossOriginIsolation` +- **Type:** `boolean` +- **Default:** `true` +- Adds `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` headers to the dev server. Required for `SharedArrayBuffer` (used by WASM workers). + +### `rpcProxyTarget` +- **Type:** `string | false` +- **Default:** `"https://rpc.testnet.miden.io"` +- gRPC-web proxy target URL for the dev server. Set to `false` to disable. + +### `rpcProxyPath` +- **Type:** `string` +- **Default:** `"/rpc.Api"` +- Path prefix for gRPC-web proxy requests. + +## Requirements + +- Vite 5.x or 6.x + +## License + +MIT diff --git a/packages/vite-plugin/package.json b/packages/vite-plugin/package.json new file mode 100644 index 0000000000..252dc5fc7a --- /dev/null +++ b/packages/vite-plugin/package.json @@ -0,0 +1,52 @@ +{ + "name": "@miden-sdk/vite-plugin", + "version": "0.13.4", + "description": "Vite plugin for Miden dApps — WASM dedup, COOP/COEP headers, and gRPC-web proxy", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "dev": "tsup src/index.ts --format cjs,esm --dts --watch", + "lint": "eslint src", + "typecheck": "tsc --noEmit" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsup": "^8.0.0", + "typescript": "^5.0.0", + "vite": "^5.0.0" + }, + "keywords": [ + "miden", + "vite", + "plugin", + "wasm", + "blockchain", + "rollup", + "zk" + ], + "author": "Miden Contributors", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/0xMiden/miden-client" + }, + "bugs": { + "url": "https://github.com/0xMiden/miden-client/issues" + } +} diff --git a/packages/vite-plugin/src/index.ts b/packages/vite-plugin/src/index.ts new file mode 100644 index 0000000000..d54857e485 --- /dev/null +++ b/packages/vite-plugin/src/index.ts @@ -0,0 +1,89 @@ +import type { Plugin } from "vite"; +import path from "path"; +import { createRequire } from "node:module"; + +export interface MidenVitePluginOptions { + /** Packages to deduplicate. Default: ["@miden-sdk/miden-sdk"] */ + wasmPackages?: string[]; + /** Enable COOP/COEP headers on dev server. Default: true */ + crossOriginIsolation?: boolean; + /** gRPC-web proxy target URL. Default: "https://rpc.testnet.miden.io". Set to false to disable. */ + rpcProxyTarget?: string | false; + /** gRPC-web proxy path prefix. Default: "/rpc.Api" */ + rpcProxyPath?: string; +} + +export function midenVitePlugin(options?: MidenVitePluginOptions): Plugin { + const { + wasmPackages = ["@miden-sdk/miden-sdk"], + crossOriginIsolation = true, + rpcProxyTarget = "https://rpc.testnet.miden.io", + rpcProxyPath = "/rpc.Api", + } = options ?? {}; + + return { + name: "@miden-sdk/vite-plugin", + enforce: "pre", + + config(userConfig, env) { + const root = userConfig.root ?? process.cwd(); + + // Use array form for resolve.alias so Vite appends rather than replaces + // any existing aliases the user may have configured. + // Use require.resolve for portable resolution in pnpm/Yarn Plug'n'Play setups. + const esmRequire = createRequire(`file://${root}/`); + const alias = wasmPackages.map((pkg) => { + let replacement: string; + try { + replacement = path.dirname(esmRequire.resolve(`${pkg}/package.json`)); + } catch { + replacement = path.resolve(root, "node_modules", pkg); + } + return { find: pkg, replacement }; + }); + + const serverConfig: Record = {}; + const previewConfig: Record = {}; + + if (crossOriginIsolation) { + const coopCoepHeaders = { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", + }; + serverConfig.headers = coopCoepHeaders; + previewConfig.headers = coopCoepHeaders; + } + + if (rpcProxyTarget !== false && env.command === "serve") { + serverConfig.proxy = { + [rpcProxyPath]: { + target: rpcProxyTarget, + changeOrigin: true, + }, + }; + } + + return { + resolve: { + alias, + dedupe: [...wasmPackages], + preserveSymlinks: true, + }, + optimizeDeps: { + exclude: [...wasmPackages], + }, + build: { + target: "esnext", + }, + worker: { + format: "es" as const, + rollupOptions: { output: { format: "es" as const } }, + }, + server: serverConfig, + preview: previewConfig, + }; + }, + }; +} + +export default midenVitePlugin; diff --git a/packages/vite-plugin/tsconfig.json b/packages/vite-plugin/tsconfig.json new file mode 100644 index 0000000000..553a8900d8 --- /dev/null +++ b/packages/vite-plugin/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020"], + "types": ["node"], + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src", + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "isolatedModules": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/vite-plugin/yarn.lock b/packages/vite-plugin/yarn.lock new file mode 100644 index 0000000000..b19f8d8c1f --- /dev/null +++ b/packages/vite-plugin/yarn.lock @@ -0,0 +1,801 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/aix-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2" + integrity sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz#19b882408829ad8e12b10aff2840711b2da361e8" + integrity sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz#90be58de27915efa27b767fcbdb37a4470627d7b" + integrity sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/android-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz#d7dcc976f16e01a9aaa2f9b938fbec7389f895ac" + integrity sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz#9f6cac72b3a8532298a6a4493ed639a8988e8abd" + integrity sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/darwin-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz#ac61d645faa37fd650340f1866b0812e1fb14d6a" + integrity sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz#b8625689d73cf1830fe58c39051acdc12474ea1b" + integrity sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/freebsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz#07be7dd3c9d42fe0eccd2ab9f9ded780bc53bead" + integrity sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz#bf31918fe5c798586460d2b3d6c46ed2c01ca0b6" + integrity sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz#28493ee46abec1dc3f500223cd9f8d2df08f9d11" + integrity sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz#750752a8b30b43647402561eea764d0a41d0ee29" + integrity sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-loong64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz#a5a92813a04e71198c50f05adfaf18fc1e95b9ed" + integrity sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-mips64el@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz#deb45d7fd2d2161eadf1fbc593637ed766d50bb1" + integrity sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz#6f39ae0b8c4d3d2d61a65b26df79f6e12a1c3d78" + integrity sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-riscv64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz#4c5c19c3916612ec8e3915187030b9df0b955c1d" + integrity sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-s390x@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz#9ed17b3198fa08ad5ccaa9e74f6c0aff7ad0156d" + integrity sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/linux-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz#12383dcbf71b7cf6513e58b4b08d95a710bf52a5" + integrity sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA== + +"@esbuild/netbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz#dd0cb2fa543205fcd931df44f4786bfcce6df7d7" + integrity sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/netbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz#028ad1807a8e03e155153b2d025b506c3787354b" + integrity sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA== + +"@esbuild/openbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz#e3c16ff3490c9b59b969fffca87f350ffc0e2af5" + integrity sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/openbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz#c5a4693fcb03d1cbecbf8b422422468dfc0d2a8b" + integrity sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ== + +"@esbuild/openharmony-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz#082082444f12db564a0775a41e1991c0e125055e" + integrity sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/sunos-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz#5ab036c53f929e8405c4e96e865a424160a1b537" + integrity sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz#38de700ef4b960a0045370c171794526e589862e" + integrity sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz#451b93dc03ec5d4f38619e6cd64d9f9eff06f55c" + integrity sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + +"@esbuild/win32-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17" + integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA== + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@rollup/rollup-android-arm-eabi@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz#a6742c74c7d9d6d604ef8a48f99326b4ecda3d82" + integrity sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg== + +"@rollup/rollup-android-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz#97247be098de4df0c11971089fd2edf80a5da8cf" + integrity sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q== + +"@rollup/rollup-darwin-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz#674852cf14cf11b8056e0b1a2f4e872b523576cf" + integrity sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg== + +"@rollup/rollup-darwin-x64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz#36dfd7ed0aaf4d9d89d9ef983af72632455b0246" + integrity sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w== + +"@rollup/rollup-freebsd-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz#2f87c2074b4220260fdb52a9996246edfc633c22" + integrity sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA== + +"@rollup/rollup-freebsd-x64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz#9b5a26522a38a95dc06616d1939d4d9a76937803" + integrity sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg== + +"@rollup/rollup-linux-arm-gnueabihf@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz#86aa4859385a8734235b5e40a48e52d770758c3a" + integrity sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw== + +"@rollup/rollup-linux-arm-musleabihf@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz#cbe70e56e6ece8dac83eb773b624fc9e5a460976" + integrity sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA== + +"@rollup/rollup-linux-arm64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz#d14992a2e653bc3263d284bc6579b7a2890e1c45" + integrity sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA== + +"@rollup/rollup-linux-arm64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz#2fdd1ddc434ea90aeaa0851d2044789b4d07f6da" + integrity sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA== + +"@rollup/rollup-linux-loong64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz#8a181e6f89f969f21666a743cd411416c80099e7" + integrity sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg== + +"@rollup/rollup-linux-loong64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz#904125af2babc395f8061daa27b5af1f4e3f2f78" + integrity sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q== + +"@rollup/rollup-linux-ppc64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz#a57970ac6864c9a3447411a658224bdcf948be22" + integrity sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA== + +"@rollup/rollup-linux-ppc64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz#bb84de5b26870567a4267666e08891e80bb56a63" + integrity sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA== + +"@rollup/rollup-linux-riscv64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz#72d00d2c7fb375ce3564e759db33f17a35bffab9" + integrity sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg== + +"@rollup/rollup-linux-riscv64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz#4c166ef58e718f9245bd31873384ba15a5c1a883" + integrity sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg== + +"@rollup/rollup-linux-s390x-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz#bb5025cde9a61db478c2ca7215808ad3bce73a09" + integrity sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w== + +"@rollup/rollup-linux-x64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz#9b66b1f9cd95c6624c788f021c756269ffed1552" + integrity sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg== + +"@rollup/rollup-linux-x64-musl@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz#b007ca255dc7166017d57d7d2451963f0bd23fd9" + integrity sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg== + +"@rollup/rollup-openbsd-x64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz#e8b357b2d1aa2c8d76a98f5f0d889eabe93f4ef9" + integrity sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ== + +"@rollup/rollup-openharmony-arm64@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz#96c2e3f4aacd3d921981329831ff8dde492204dc" + integrity sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA== + +"@rollup/rollup-win32-arm64-msvc@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz#2d865149d706d938df8b4b8f117e69a77646d581" + integrity sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A== + +"@rollup/rollup-win32-ia32-msvc@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz#abe1593be0fa92325e9971c8da429c5e05b92c36" + integrity sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA== + +"@rollup/rollup-win32-x64-gnu@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz#c4af3e9518c9a5cd4b1c163dc81d0ad4d82e7eab" + integrity sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA== + +"@rollup/rollup-win32-x64-msvc@4.59.0": + version "4.59.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c" + integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== + +"@types/estree@1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/node@^20.0.0": + version "20.19.35" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.35.tgz#117b373fd1dff528b2f9f8c2d1a85de6af8101ca" + integrity sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ== + dependencies: + undici-types "~6.21.0" + +acorn@^8.15.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +bundle-require@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-5.1.0.tgz#8db66f41950da3d77af1ef3322f4c3e04009faee" + integrity sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA== + dependencies: + load-tsconfig "^0.2.3" + +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +confbox@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" + integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + +consola@^3.4.0: + version "3.4.2" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.2.tgz#5af110145397bb67afdab77013fdc34cae590ea7" + integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== + +debug@^4.4.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + +esbuild@^0.27.0: + version "0.27.3" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.3.tgz#5859ca8e70a3af956b26895ce4954d7e73bd27a8" + integrity sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.27.3" + "@esbuild/android-arm" "0.27.3" + "@esbuild/android-arm64" "0.27.3" + "@esbuild/android-x64" "0.27.3" + "@esbuild/darwin-arm64" "0.27.3" + "@esbuild/darwin-x64" "0.27.3" + "@esbuild/freebsd-arm64" "0.27.3" + "@esbuild/freebsd-x64" "0.27.3" + "@esbuild/linux-arm" "0.27.3" + "@esbuild/linux-arm64" "0.27.3" + "@esbuild/linux-ia32" "0.27.3" + "@esbuild/linux-loong64" "0.27.3" + "@esbuild/linux-mips64el" "0.27.3" + "@esbuild/linux-ppc64" "0.27.3" + "@esbuild/linux-riscv64" "0.27.3" + "@esbuild/linux-s390x" "0.27.3" + "@esbuild/linux-x64" "0.27.3" + "@esbuild/netbsd-arm64" "0.27.3" + "@esbuild/netbsd-x64" "0.27.3" + "@esbuild/openbsd-arm64" "0.27.3" + "@esbuild/openbsd-x64" "0.27.3" + "@esbuild/openharmony-arm64" "0.27.3" + "@esbuild/sunos-x64" "0.27.3" + "@esbuild/win32-arm64" "0.27.3" + "@esbuild/win32-ia32" "0.27.3" + "@esbuild/win32-x64" "0.27.3" + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +fix-dts-default-cjs-exports@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz#955cb6b3d519691c57828b078adadf2cb92e9549" + integrity sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg== + dependencies: + magic-string "^0.30.17" + mlly "^1.7.4" + rollup "^4.34.8" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +joycon@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== + +lilconfig@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +load-tsconfig@^0.2.3: + version "0.2.5" + resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.5.tgz#453b8cd8961bfb912dea77eb6c168fe8cca3d3a1" + integrity sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg== + +magic-string@^0.30.17: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +mlly@^1.7.4: + version "1.8.0" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.8.0.tgz#e074612b938af8eba1eaf43299cbc89cb72d824e" + integrity sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g== + dependencies: + acorn "^8.15.0" + pathe "^2.0.3" + pkg-types "^1.3.1" + ufo "^1.6.1" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +pathe@^2.0.1, pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +pirates@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-types@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df" + integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== + dependencies: + confbox "^0.1.8" + mlly "^1.7.4" + pathe "^2.0.1" + +postcss-load-config@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096" + integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== + dependencies: + lilconfig "^3.1.1" + +postcss@^8.4.43: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +rollup@^4.20.0, rollup@^4.34.8: + version "4.59.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.59.0.tgz#cf74edac17c1486f562d728a4d923a694abdf06f" + integrity sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg== + dependencies: + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.59.0" + "@rollup/rollup-android-arm64" "4.59.0" + "@rollup/rollup-darwin-arm64" "4.59.0" + "@rollup/rollup-darwin-x64" "4.59.0" + "@rollup/rollup-freebsd-arm64" "4.59.0" + "@rollup/rollup-freebsd-x64" "4.59.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.59.0" + "@rollup/rollup-linux-arm-musleabihf" "4.59.0" + "@rollup/rollup-linux-arm64-gnu" "4.59.0" + "@rollup/rollup-linux-arm64-musl" "4.59.0" + "@rollup/rollup-linux-loong64-gnu" "4.59.0" + "@rollup/rollup-linux-loong64-musl" "4.59.0" + "@rollup/rollup-linux-ppc64-gnu" "4.59.0" + "@rollup/rollup-linux-ppc64-musl" "4.59.0" + "@rollup/rollup-linux-riscv64-gnu" "4.59.0" + "@rollup/rollup-linux-riscv64-musl" "4.59.0" + "@rollup/rollup-linux-s390x-gnu" "4.59.0" + "@rollup/rollup-linux-x64-gnu" "4.59.0" + "@rollup/rollup-linux-x64-musl" "4.59.0" + "@rollup/rollup-openbsd-x64" "4.59.0" + "@rollup/rollup-openharmony-arm64" "4.59.0" + "@rollup/rollup-win32-arm64-msvc" "4.59.0" + "@rollup/rollup-win32-ia32-msvc" "4.59.0" + "@rollup/rollup-win32-x64-gnu" "4.59.0" + "@rollup/rollup-win32-x64-msvc" "4.59.0" + fsevents "~2.3.2" + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map@^0.7.6: + version "0.7.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02" + integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== + +sucrase@^3.35.0: + version "3.35.1" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.1.tgz#4619ea50393fe8bd0ae5071c26abd9b2e346bfe1" + integrity sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + tinyglobby "^0.2.11" + ts-interface-checker "^0.1.9" + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinyglobby@^0.2.11: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +tsup@^8.0.0: + version "8.5.1" + resolved "https://registry.yarnpkg.com/tsup/-/tsup-8.5.1.tgz#a9c7a875b93344bdf70600dedd78e70f88ec9a65" + integrity sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing== + dependencies: + bundle-require "^5.1.0" + cac "^6.7.14" + chokidar "^4.0.3" + consola "^3.4.0" + debug "^4.4.0" + esbuild "^0.27.0" + fix-dts-default-cjs-exports "^1.0.0" + joycon "^3.1.1" + picocolors "^1.1.1" + postcss-load-config "^6.0.1" + resolve-from "^5.0.0" + rollup "^4.34.8" + source-map "^0.7.6" + sucrase "^3.35.0" + tinyexec "^0.3.2" + tinyglobby "^0.2.11" + tree-kill "^1.2.2" + +typescript@^5.0.0: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +ufo@^1.6.1: + version "1.6.3" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.3.tgz#799666e4e88c122a9659805e30b9dc071c3aed4f" + integrity sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +vite@^5.0.0: + version "5.4.21" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027" + integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" diff --git a/scripts/check-react-sdk-sync.js b/scripts/check-react-sdk-sync.js deleted file mode 100644 index 4acb80e5d4..0000000000 --- a/scripts/check-react-sdk-sync.js +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env node -"use strict"; - -const fs = require("fs"); -const path = require("path"); - -const repoRoot = path.resolve(__dirname, ".."); -const webClientPath = path.join( - repoRoot, - "crates", - "web-client", - "package.json" -); -const reactSdkPath = path.join( - repoRoot, - "packages", - "react-sdk", - "package.json" -); -const walletExamplePath = path.join( - repoRoot, - "packages", - "react-sdk", - "examples", - "wallet", - "package.json" -); - -const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, "utf8")); - -const writeJson = (filePath, data) => { - fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`); -}; - -const webClientPkg = readJson(webClientPath); -const reactSdkPkg = readJson(reactSdkPath); -const walletExamplePkg = readJson(walletExamplePath); - -const webClientVersion = webClientPkg.version; -const versionMatch = /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(webClientVersion); - -if (!versionMatch) { - console.error(`Unsupported web-client version format: "${webClientVersion}"`); - process.exit(1); -} - -const major = Number(versionMatch[1]); -const minor = Number(versionMatch[2]); -const expectedRange = `^${major}.${minor}.0`; - -const peerDeps = reactSdkPkg.peerDependencies || {}; -const actualRange = peerDeps["@miden-sdk/miden-sdk"]; -const actualVersion = reactSdkPkg.version; -const walletDeps = walletExamplePkg.dependencies || {}; -const walletRange = walletDeps["@miden-sdk/miden-sdk"]; -const shouldFix = process.argv.includes("--fix"); -const errors = []; - -if (!actualRange) { - errors.push( - "Missing peerDependencies entry for @miden-sdk/miden-sdk in react-sdk." - ); -} - -if (actualRange !== expectedRange) { - errors.push( - `React SDK peer range "${actualRange}" does not match expected "${expectedRange}" for web-client ${webClientVersion}.` - ); -} - -const reactVersionMatch = /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(actualVersion); -if (!reactVersionMatch) { - errors.push(`Unsupported react-sdk version format: "${actualVersion}"`); -} else if ( - Number(reactVersionMatch[1]) !== major || - Number(reactVersionMatch[2]) !== minor -) { - errors.push( - `React SDK version "${actualVersion}" has different major.minor than web-client "${webClientVersion}". They must share the same major.minor version.` - ); -} - -if (!walletRange) { - errors.push( - "Missing dependencies entry for @miden-sdk/miden-sdk in wallet example." - ); -} - -if (walletRange !== expectedRange) { - errors.push( - `Wallet example dependency "${walletRange}" does not match expected "${expectedRange}" for web-client ${webClientVersion}.` - ); -} - -if (errors.length > 0) { - if (shouldFix) { - let updated = false; - if (actualRange !== expectedRange) { - peerDeps["@miden-sdk/miden-sdk"] = expectedRange; - reactSdkPkg.peerDependencies = peerDeps; - updated = true; - } - - if (walletRange !== expectedRange) { - walletDeps["@miden-sdk/miden-sdk"] = expectedRange; - walletExamplePkg.dependencies = walletDeps; - updated = true; - } - - if (updated) { - writeJson(reactSdkPath, reactSdkPkg); - writeJson(walletExamplePath, walletExamplePkg); - console.log( - `Updated react-sdk peer range to "${expectedRange}" and wallet dependency based on web-client ${webClientVersion}.` - ); - } - - process.exit(0); - } - - for (const message of errors) { - console.error(message); - } - process.exit(1); -} - -console.log( - `React SDK version/peer range and wallet dependency match web-client ${webClientVersion} (${expectedRange}).` -);