From 0e406478f177c5a3354b73f6041488feb42f7335 Mon Sep 17 00:00:00 2001 From: Charlie Sibbach Date: Tue, 14 Jul 2026 17:16:53 -0700 Subject: [PATCH 01/41] Initial bootstrap. Enhance README with detailed project structure, setup instructions, and development guidelines for the OWS Branding Layer of the 1Shot Wallet. --- .agents/skills/ows-branding-layer/SKILL.md | 174 + .../references/architecture.md | 53 + .../ows-branding-layer/references/packages.md | 70 + .../ows-branding-layer/references/tasks.md | 155 + .dockerignore | 8 + .env.example | 10 + Dockerfile | 27 + README.md | 74 +- components.json | 25 + index.html | 12 + nginx.conf | 30 + package-lock.json | 7789 +++++++++++++++++ package.json | 41 + scripts/clean.mjs | 8 + scripts/copy-signer.mjs | 23 + scripts/dev.mjs | 141 + scripts/sync-signer.mjs | 37 + signer-static/index.html | 54 + skills-lock.json | 10 + src/App.tsx | 13 + src/components/ui/button.tsx | 67 + src/index.css | 130 + src/lib/utils.ts | 6 + src/main.tsx | 10 + src/vite-env.d.ts | 1 + tsconfig.json | 25 + tsconfig.node.json | 13 + vendor/ows-signer/package.json | 37 + vendor/ows-signer/src/constants.js | 30 + vendor/ows-signer/src/crypto/aes256.js | 115 + vendor/ows-signer/src/crypto/prf.js | 119 + vendor/ows-signer/src/crypto/recovery.js | 81 + vendor/ows-signer/src/crypto/sign.js | 59 + .../src/crypto/vendor/noble-ed25519.js | 385 + .../src/crypto/vendor/noble-secp256k1.js | 602 ++ vendor/ows-signer/src/debug.js | 42 + vendor/ows-signer/src/handlers.js | 691 ++ vendor/ows-signer/src/hex.js | 68 + vendor/ows-signer/src/main.js | 40 + vendor/ows-signer/src/rpc.js | 48 + vendor/ows-signer/src/state.js | 96 + vendor/ows-signer/src/ui.js | 68 + vendor/ows-signer/src/webauthn.js | 150 + vite.config.ts | 131 + 44 files changed, 11767 insertions(+), 1 deletion(-) create mode 100644 .agents/skills/ows-branding-layer/SKILL.md create mode 100644 .agents/skills/ows-branding-layer/references/architecture.md create mode 100644 .agents/skills/ows-branding-layer/references/packages.md create mode 100644 .agents/skills/ows-branding-layer/references/tasks.md create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 Dockerfile create mode 100644 components.json create mode 100644 index.html create mode 100644 nginx.conf create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/clean.mjs create mode 100644 scripts/copy-signer.mjs create mode 100644 scripts/dev.mjs create mode 100644 scripts/sync-signer.mjs create mode 100644 signer-static/index.html create mode 100644 skills-lock.json create mode 100644 src/App.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/index.css create mode 100644 src/lib/utils.ts create mode 100644 src/main.tsx create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vendor/ows-signer/package.json create mode 100644 vendor/ows-signer/src/constants.js create mode 100644 vendor/ows-signer/src/crypto/aes256.js create mode 100644 vendor/ows-signer/src/crypto/prf.js create mode 100644 vendor/ows-signer/src/crypto/recovery.js create mode 100644 vendor/ows-signer/src/crypto/sign.js create mode 100644 vendor/ows-signer/src/crypto/vendor/noble-ed25519.js create mode 100644 vendor/ows-signer/src/crypto/vendor/noble-secp256k1.js create mode 100644 vendor/ows-signer/src/debug.js create mode 100644 vendor/ows-signer/src/handlers.js create mode 100644 vendor/ows-signer/src/hex.js create mode 100644 vendor/ows-signer/src/main.js create mode 100644 vendor/ows-signer/src/rpc.js create mode 100644 vendor/ows-signer/src/state.js create mode 100644 vendor/ows-signer/src/ui.js create mode 100644 vendor/ows-signer/src/webauthn.js create mode 100644 vite.config.ts diff --git a/.agents/skills/ows-branding-layer/SKILL.md b/.agents/skills/ows-branding-layer/SKILL.md new file mode 100644 index 0000000..5a82a5a --- /dev/null +++ b/.agents/skills/ows-branding-layer/SKILL.md @@ -0,0 +1,174 @@ +--- +name: ows-branding-layer +description: >- + Build an Open Wallet Standard (OWS) Branding Layer with @1shotapi/ows-wallet-utils, + ows-signer-utils, ows-types, and optional ows-oid4. Use when scaffolding a branding + wallet, wiring OWSWallet/OWSSigner, RpcHelper/SignHelper/CredentialsHelper, passkey + unlock, custom host RPC, signing consent, recovery overlay, credentials, or display + shell in a separate repo. +license: MIT +metadata: + author: 1Shot-API + version: "0.3.0" + repository: https://github.com/1Shot-API/open-wallet +--- + +# OWS Branding Layer + +Teach an agent how to scaffold and extend an **OWS Branding Layer** in any repository. + +UI, flow ownership, and display queuing stay **app-owned**. Published packages provide thin protocol helpers (`RpcHelper`, `SignHelper`, `CredentialsHelper`, `overlaySignerIframe`) — not a module/registry runtime. + +When a local clone of [open-wallet](https://github.com/1Shot-API/open-wallet) is available (multi-root workspace), prefer grepping `examples/general-wallet` over guessing from this skill alone. + +## Install this skill (consumer repo) + +```bash +npx skills add 1Shot-API/open-wallet@ows-branding-layer +# or: npx skills add https://github.com/1Shot-API/open-wallet --skill ows-branding-layer +# or from a sibling clone: npx skills add ../open-wallet --skill ows-branding-layer +``` + +Then invoke `/ows-branding-layer` or ask to build a branding wallet. + +## Architecture (non-negotiable) + +``` +Host Layer @1shotapi/ows-provider EIP-1193 / credentials proxy + └── Branding THIS APP UX + Postmate child + helpers + └── Signing @1shotapi/ows-signer WebAuthn PRF custody (nested iframe) +``` + +- Host **never** embeds the Signing Layer. Always Host → Branding → Signing. +- Signing Layer accepts `postMessage` only from `window.parent` when `window.parent !== window.top`. +- Passkeys require a **secure context on the entire ancestor chain** — Host and Branding/Signer must be HTTPS (or `localhost` / `*.localhost`). +- `allowLocalAccess` is a **host** `OWSProxy` option (LAN OID4 from a public branding origin). Do not put it on branding APIs. + +Details: [references/architecture.md](references/architecture.md) + +## Packages + +| Package | Role | +|---------|------| +| `@1shotapi/ows-types` | Branded primitives, errors, credential types, EIP-1193 tables, `CredentialCryptoUtils` / `PresentationUtils` / `ProofUtils` | +| `@1shotapi/ows-wallet-utils` | Branding ↔ Host (`OWSWallet`, `RpcHelper`, `requestDisplay`) | +| `@1shotapi/ows-signer-utils` | Branding ↔ Signing (`OWSSigner`, `SignHelper`, `overlaySignerIframe`) | +| `@1shotapi/ows-oid4` | Optional credentials (`CredentialsHelper`, HTTP OID4VCI/OID4VP clients) | +| `@1shotapi/ows-signer` | Plain JS Signing Layer sources (serve as static `/signer/`) | + +```bash +npm install \ + @1shotapi/ows-types \ + @1shotapi/ows-wallet-utils \ + @1shotapi/ows-signer-utils \ + @1shotapi/ows-oid4 \ + viem +# zod is transitive via ows-wallet-utils — add a direct dep only for custom registerRpc schemas +# Serve/copy ows-signer HTML+JS from the same origin as branding (rpId = signer hostname) +``` + +Details: [references/packages.md](references/packages.md) + +## Task checklist + +Copy and track progress. Order matches a typical first branding app; specialized wallets may skip EIP-1193 and lead with custom host RPC. The reference wallet (`examples/general-wallet`) is **EIP-1193-first**. + +``` +Branding Layer Progress: +- [ ] 1. Scaffold — packages + serve Signing Layer on same origin at /signer/ +- [ ] 2. Unlock — passkey create/login (`awaitSignerReady` vs `ensureReady`) +- [ ] 3. Display shell — requestDisplay / hide + app-owned modal queue +- [ ] 4. Host RPC — EIP-1193 (RpcHelper + account connect) and/or custom registerRpc +- [ ] 5. Signing consent — SignHelper + app approval UI +- [ ] 6. Recovery overlay — create/restore with overlaySignerIframe (no reparent) +- [ ] 7. Credentials — CredentialsHelper.register + consent UI (optional) +``` + +Task details + exact example paths: [references/tasks.md](references/tasks.md) + +### Minimal boot sequence + +Stock Postmate parents only retry the handshake briefly after branding iframe `load`. **Register `Postmate.Model` via `wallet.start()` before the nested Signing Layer finishes loading.** Use a deferred signer proxy so helpers can register with an `OWSSigner`-shaped object immediately. + +Canonical implementation: `examples/general-wallet/src/wallet/WalletProvider.tsx` (`createDeferredSigner` + `boot`). + +```typescript +import { OWSSigner, SignHelper } from "@1shotapi/ows-signer-utils"; +import { OWSWallet, RpcHelper } from "@1shotapi/ows-wallet-utils"; +// Optional credentials: +// import { CredentialsHelper } from "@1shotapi/ows-oid4"; + +const wallet = OWSWallet.prepare({ debug: false }); + +const signerPromise = OWSSigner.create(signerContainer, signerUrl, { + hidden: true, + credentialId: loadCredentialId(), +}); +// Proxy that throws until signerPromise resolves — do NOT await signer before start(). +const signer = createDeferredSigner(() => signerPromise); // copy pattern from WalletProvider + +// Register before start (order in general-wallet): +// account connect → SignHelper handlers → CredentialsHelper.register() → RpcHelper +new RpcHelper(providers, wallet, signer, { defaultChainId }); + +const signHelper = new SignHelper(signer, wallet, { + ensureReady: async () => { + await signerPromise; // iframe load + await unlockIfNeeded(); // passkey create/login + }, + requestPersonalSignApproval, + requestSignTypedDataApproval, +}); +for (const [method, handler] of Object.entries(signHelper.handlers)) { + wallet.registerEip1193(method, handler); +} + +// Optional: +// new CredentialsHelper(wallet, signer, { … }).register(); +// Note arg order: CredentialsHelper(wallet, signer) vs SignHelper(signer, wallet) + +void wallet.start(); // registers Model immediately — do not await nested signer first +void signerPromise; // background load; UI can paint +``` + +Prefer **`OWSWallet.prepare()` → register handlers → `start()`**. Do not use a module install runtime. + +Split readiness in your app: + +| API | Meaning | +|-----|---------| +| `awaitSignerReady()` | Nested Signing Layer iframe + `OWSSigner` loaded | +| `ensureReady()` | Signer loaded **and** unlocked / onboarded (passkey) | + +Restore-backup must use **`awaitSignerReady` only** — calling `ensureReady` first can force setup/login before recovery. + +## Hard rules (from OWS) + +1. **Branded types** from `@1shotapi/ows-types` — use constructors (`EVMAccountAddress(...)`), never `as` casts. +2. **No deprecations** during pre-1.0 — rename and update all call sites. +3. Prefer **methods on objects** over free helpers when logic belongs to one class. +4. Set iframe `allow` for WebAuthn/clipboard **before** navigation (`OWSSigner` / host `OWSProxy` already do this). +5. Do not vendor Postmate — use the `postmate` package (transitive). +6. Do **not** reparent the signer iframe for passphrase UI — use `overlaySignerIframe`. +7. Prefer Vite `/signer/` at **origin root** even if the branding app uses a subpath `base` (e.g. `/wallet/`). + +## Reference implementation + +Canonical React + Vite + Tailwind demo: `examples/general-wallet` in [1Shot-API/open-wallet](https://github.com/1Shot-API/open-wallet). + +| Concern | Start here | +|---------|------------| +| Boot / deferred signer | `src/wallet/WalletProvider.tsx` | +| Account connect | `src/ows/registerAccountConnect.ts` | +| Sign consent | `src/ows/registerApprovalSigning.ts`, `src/components/modals/SignModals.tsx` | +| Credentials | `src/ows/registerCredentialsProvider.ts`, `src/components/modals/CredentialModals.tsx` | +| Recovery | `src/components/modals/BackupModals.tsx` | +| Serve signer | `vite.config.ts`, `scripts/copy-signer.mjs`, `signer-static/index.html` | + +## Out of scope for this skill + +- Host Layer apps (`ows-provider`) — separate concern (`allowLocalAccess`, flyout chrome) +- Implementing or modifying `ows-signer` internals +- Publishing npm packages / changesets +- Shared React/Tailwind UI kits (apps own their UI) +- Calling `PresentationUtils` / SD-JWT low-level APIs unless you bypass `CredentialsHelper` diff --git a/.agents/skills/ows-branding-layer/references/architecture.md b/.agents/skills/ows-branding-layer/references/architecture.md new file mode 100644 index 0000000..6498f17 --- /dev/null +++ b/.agents/skills/ows-branding-layer/references/architecture.md @@ -0,0 +1,53 @@ +# OWS Branding Layer architecture notes + +## Three layers + +| Layer | Runs where | Trust / UX | +|-------|------------|------------| +| Host | Integrator dapp | EIP-1193 / credentials proxy; must be secure context if it embeds HTTPS wallet | +| Branding | Wallet origin (your product) | Branding, consent, app-owned UI + SDK helpers | +| Signing | Nested iframe (prefer same origin as branding) | Passkeys / PRF; minimal attack surface | + +## Origins and `rpId` + +- Signing Layer `rpId` is always `window.location.hostname` of the signer document. +- Prefer **same origin** for branding + signer (shared passkey namespace). +- Reference layout: branding UI may live under a path (`/wallet/`) while signer document is at `/signer/` on the **same origin**. +- Canonical CDN / on-chain gateway is an alternative shared `rpId` for all integrators. + +## Secure contexts + +Browsers require the **entire iframe ancestor chain** to be secure for WebAuthn. Therefore: + +- Production hosts: HTTPS +- Local custom hosts (`ows-host.com`): HTTPS via mkcert (see `examples/host` in open-wallet) +- `http://localhost` remains a special-case secure context +- Branding demos often use ngrok HTTPS (`examples/general-wallet/scripts/dev.mjs`) + +## Display protocol + +Branding: + +1. `const display = await wallet.requestDisplay({ width, height })` +2. Run WebAuthn / approval UI inside the branding iframe +3. `await display.hide()` (or `wallet.requestHide()`) + +Host `OWSProxy` shows a lower-right opaque flyout (no modal backdrop). Protocol events include `ows:requestDisplay`, `ows:requestHide`, and `ows:releaseDisplay` (session `release()` / hide). Apps should not invent parallel display protocols. + +Display **queuing** (serializing concurrent modals) is app-owned — see `WalletProvider` modal queue in general-wallet. + +## Host handshake vs nested signer + +Stock Postmate child handshake windows are short. Host-side `OWSProxy` extends the handshake timeout for slow tunnels, but branding must still **`wallet.start()` before awaiting the nested Signing Layer** so the parent finds a Model. Deferred signer proxies keep handlers registerable immediately. + +## Host-only: Local Network Access + +When a public branding origin (e.g. ngrok) fetches private/loopback OID4 endpoints, Chrome may require the host iframe `allow` attribute. That is configured on **`OWSProxy.create(..., { allowLocalAccess: true })`**, not on branding wallet APIs. + +## Integrity (future) + +Signed-manifest / verify-before-execute for `ows-signer` is planned (see `packages/ows-signer/docs/trusted-loader-plan.md`). Not required for a first branding scaffold. + +## Reference demo + +`examples/general-wallet` + `examples/host` (and credential issuer/verifier hosts) in the open-wallet monorepo demonstrate Host → Branding → Signing with HTTPS passkeys. diff --git a/.agents/skills/ows-branding-layer/references/packages.md b/.agents/skills/ows-branding-layer/references/packages.md new file mode 100644 index 0000000..7f74748 --- /dev/null +++ b/.agents/skills/ows-branding-layer/references/packages.md @@ -0,0 +1,70 @@ +# OWS packages for a Branding Layer + +## Install + +Aligned with `examples/general-wallet/package.json`: + +```bash +npm install \ + @1shotapi/ows-types \ + @1shotapi/ows-wallet-utils \ + @1shotapi/ows-signer-utils \ + @1shotapi/ows-oid4 \ + viem +``` + +Notes: + +- `viem` is a peer of `ows-signer-utils` (EVM helpers). +- `postmate` and `zod` are **transitive** via `ows-wallet-utils` — do not vendor Postmate; add a direct `zod` dep only if you author custom `registerRpc` schemas. +- `@1shotapi/ows-signer` is plain JS sources. Prefer **copy or static-serve** into your app (no build step). Same origin as branding so `rpId === location.hostname`. Consuming it solely as an npm import of TS modules is not the reference pattern. +- `@1shotapi/ows-provider` is for **host** apps only — omit from a branding-only package unless the same origin also serves a demo host. + +Until packages are on npm, depend via workspace, `file:`, or git: + +```bash +# example — sibling clone +npm install ../open-wallet/packages/ows-types +``` + +## Package roles + +| Package | Import surface | Branding uses it for | +|---------|----------------|----------------------| +| `ows-types` | primitives, errors, credentials, EIP-1193 tables, `CredentialCryptoUtils`, `PresentationUtils`, `ProofUtils` | Branded values, holder signer bridge, shared errors | +| `ows-wallet-utils` | `OWSWallet`, `RpcHelper`, display child client | Postmate child, EIP-1193 / custom RPC registration, reads/chain, `requestDisplay` | +| `ows-signer-utils` | `OWSSigner`, `SignHelper`, `overlaySignerIframe`, `evm.*` | Nested signer iframe, consent→sign wiring, digests → signatures | +| `ows-oid4` | `CredentialsHelper`, `HttpOid4vciClient`, `HttpOid4vpClient`, … | Optional OID4 accept/present orchestration | +| `ows-signer` | static files | Custody kernel under `/signer/` | + +There is **no** branding-core / registry package. App-local UI and wiring live in your repo (see `examples/general-wallet/src/ows/`). + +### Constructor arg order (easy to swap) + +| Helper | Order | +|--------|-------| +| `SignHelper` | `(signer, wallet, options)` | +| `CredentialsHelper` | `(wallet, signer, options)` | + +## Serving the Signing Layer + +Mirror `examples/general-wallet`: + +- Branding app may use Vite `base: "/wallet/"` for the UI. +- Signer is served at **`/signer/` outside that base** via middleware so WebAuthn + nest stay same-origin (`vite.config.ts` `serveSignerPlugin`). +- HTML shell: `signer-static/index.html`; JS from package `src/`. +- Prod: `scripts/copy-signer.mjs` → `dist/signer/`. + +```typescript +const signerUrl = new URL("/signer/", window.location.origin).href; +``` + +## Credentials stack (optional) + +Prefer `CredentialsHelper` + HTTP clients from **`@1shotapi/ows-oid4`**. Demo-only stores / trust / keys live under `examples/shared` in open-wallet (not published) — product apps supply their own `ICredentialRepository`, trust registry, and OID4 clients. + +Holder signing: omit `holderSigner` to use the default OWS Ed25519 bridge (`CredentialCryptoUtils.createOwsEd25519HolderSigner` in `ows-types`), or pass your own `IHolderSigner`. + +## Host apps (not branding) + +Hosts use `@1shotapi/ows-provider` (`OWSProxy`) only. Host-only options include `allowLocalAccess` (iframe permissions for Local Network Access when branding on a public origin fetches `127.0.0.1` / private LAN OID4 endpoints). diff --git a/.agents/skills/ows-branding-layer/references/tasks.md b/.agents/skills/ows-branding-layer/references/tasks.md new file mode 100644 index 0000000..fe4f64f --- /dev/null +++ b/.agents/skills/ows-branding-layer/references/tasks.md @@ -0,0 +1,155 @@ +# Branding Layer tasks + +Task-oriented guidance aligned with `examples/general-wallet`. Specialize freely; production branding apps may lead with **custom host RPC** and skip MetaMask-shaped EIP-1193. The reference wallet is **EIP-1193-first**. + +Paths below are under `examples/general-wallet/` unless noted. + +## 1. Scaffold + +1. Install packages (see [packages.md](packages.md)). +2. Serve `@1shotapi/ows-signer` as static **`/signer/` on the same origin** as branding (`rpId === location.hostname`). + - Dev: Vite middleware maps `/signer/` → `signer-static/index.html` and `/signer/src/` → package `src/` (**outside** Vite `base`, e.g. `/wallet/`). See `vite.config.ts`. + - Prod: `scripts/copy-signer.mjs` into `dist/signer/`. +3. Signer URL: `new URL("/signer/", window.location.origin).href` (origin root, not under app `base`). +4. Create a hidden signer host (`src/components/SignerHost.tsx`), then **start Postmate before awaiting nested signer load**: + +```typescript +const wallet = OWSWallet.prepare(); +const signerPromise = OWSSigner.create(container, signerUrl, { + hidden: true, + credentialId: loadCredentialId(), +}); +const signer = createDeferredSigner(() => signerPromise); // WalletProvider.tsx +// …register handlers (ensureReady awaits signerPromise + unlock)… +void wallet.start(); // registers Model immediately +void signerPromise; // background — do not block UI/paint on this +``` + +Also see `scripts/dev.mjs` (ngrok HTTPS for passkeys). Do not run leftover `--no-tunnel` processes on the same port while hosts expect a tunnel URL. + +## 2. Unlock (`awaitSignerReady` vs `ensureReady`) + +App-owned passkey create / login. Split readiness: + +| Helper | Waits for | +|--------|-----------| +| `awaitSignerReady()` | Signing Layer iframe + `OWSSigner` instance | +| `ensureReady()` | Signer ready **plus** unlocked / wallet created | + +- Persist credential id + cached addresses in app storage (`src/storage.ts`). +- Gate signing and account-connect behind `ensureReady()`. +- Embedded first-run UI when `window.parent !== window.top` is optional — `OnboardingPanel.tsx` / `WalletProvider.tsx` `runSetupFlow`. +- Host-driven setup: wrap with `requestDisplay` + setup modals (`SetupModals.tsx`). + +No published SDK for setup dialogs; keep UI local. + +## 3. Display shell + +Before WebAuthn or consent UI in a cross-origin host embed: + +```typescript +const display = await wallet.requestDisplay({ width, height }); +try { + // dialogs / passkey / overlay +} finally { + await display.hide(); +} +``` + +Also available: `wallet.requestHide()`. Host wire event `ows:releaseDisplay` is reached via `DisplaySession.release()` / hide paths — branding apps should prefer `display.hide()`. + +Host `OWSProxy` shows a lower-right opaque flyout (no modal backdrop). + +**App concerns (not SDK):** + +- **Modal queue** — serialize concurrent dialogs (`WalletProvider` `pushModal` / `ModalHost.tsx`). +- Nested `requestDisplay` may reuse an active session (depth). Helpers (`SignHelper`, `CredentialsHelper`) already call `requestDisplay` — **do not double-wrap** those handler paths with another outer display. + +## 4. Host RPC (primary for specialized wallets) + EIP-1193 + +**Specialized wallets:** register custom methods the host calls via `proxy.rpc` / your protocol (`wallet.registerRpc`). + +**Reference wallet path (EIP-1193):** + +1. `src/ows/registerAccountConnect.ts` — `eth_accounts` / `eth_requestAccounts` (cached addresses; connect consent + `ensureReady`). +2. `RpcHelper` for JSON-RPC reads / `wallet_switchEthereumChain` (`src/ows/demoChains.ts`, construct in `WalletProvider.tsx`). +3. `SignHelper` for `personal_sign` / typed data (task 5). + +```typescript +new RpcHelper( + new Map([[chainId, rpcUrl] /* … */]), + wallet, + signer, // optional; unused for reads today + { defaultChainId }, +); +``` + +Call after `prepare()`, before `start()`. Zod EIP-1193 / credential wire schemas live in `ows-wallet-utils` (transitive). + +## 5. Signing consent + +Headless wiring in `SignHelper` (`@1shotapi/ows-signer-utils`): + +``` +requestDisplay → consent UI → ensureReady → signer.evm.signMessage | signTypedData → hide +``` + +Registers: `personal_sign`, `eth_signTypedData`, `eth_signTypedData_v3`, `eth_signTypedData_v4`. Reject with `OwsUserRejectedError`. No transaction signing in `SignHelper`. + +```typescript +const signHelper = new SignHelper(signer, wallet, { + ensureReady, + requestPersonalSignApproval, // PersonalSignApprovalRequest → boolean + requestSignTypedDataApproval, // SignTypedDataApprovalRequest → boolean +}); +for (const [method, handler] of Object.entries(signHelper.handlers)) { + wallet.registerEip1193(method, handler); +} +``` + +Reference: `src/ows/registerApprovalSigning.ts`, `src/components/modals/SignModals.tsx`. + +## 6. Recovery overlay + +Create / restore encrypted backup — **never reparent** the signer iframe. + +Reference: `src/components/modals/BackupModals.tsx`, called from `WalletProvider` `openCreateBackup` / `openRestoreBackup`. + +1. Outer `wallet.requestDisplay` for the dialog shell (WalletProvider wrappers). +2. `overlaySignerIframe(iframe, slot, { homeContainer })` so the passphrase UI appears over a slot. +3. **Double `requestAnimationFrame`** after overlay before calling signer RPCs (layout must settle). +4. Create: `ensureReady()` then `signer.createRecoveryData(passwordText, buttonText, minPasswordLength)`. +5. Restore: **`awaitSignerReady()` only** (not `ensureReady`) then `signer.recoverKey(encrypted, passwordText, buttonText)`. +6. Always restore overlay styles in `finally` / abort cleanup; then `display.hide()`. + +Distinct from `prepareSignerIframeForWebAuthn` (1×1 invisible passkey focus used **inside** `OWSSigner`). + +## 7. Credentials (optional) + +Prefer `CredentialsHelper` from `@1shotapi/ows-oid4` (not exported from `ows-wallet-utils`): + +```typescript +new CredentialsHelper(wallet, signer, { + repository, + oid4vci, + oid4vp, + trust, + ensureReady, + requestCredentialOfferApproval, + requestCredentialPresentationApproval, + // holderSigner optional — defaults via CredentialCryptoUtils.createOwsEd25519HolderSigner +}).register(); +``` + +Constructor arg order: **`(wallet, signer, options)`** — opposite of `SignHelper(signer, wallet, …)`. + +Reference stack: + +- Wiring: `src/ows/registerCredentialsProvider.ts` +- Consent UI: `src/components/modals/CredentialModals.tsx` +- Demo repositories / trust / HTTP clients fixtures: `examples/shared` (alias `@ows-shared` in the monorepo — not published) +- Wire methods: `credentials.acceptOffer` / `present` / `list` / `delete` + +Do **not** call `PresentationUtils` / low-level SD-JWT helpers unless you bypass `CredentialsHelper`. Holder KB JWT lives in `CredentialCryptoUtils.createOwsEd25519HolderSigner` (`@1shotapi/ows-types`). + +Host demos: `examples/credential-issuer`, `examples/credential-verifier` (use `ows-provider`; `allowLocalAccess` is host-only). diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5631ebb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +node_modules +dist +.env +.git +.agents +*.md +.DS_Store +.dockerignore diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..93955eb --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +# Ngrok (required for HTTPS passkey testing with a host dapp) +# https://dashboard.ngrok.com/get-started/your-authtoken +NGROK_AUTHTOKEN= + +# Optional reserved domain (hostname only or full URL) +# https://dashboard.ngrok.com/domains +# NGROK_DOMAIN=your-name.ngrok-free.app + +# Optional Vite port (default 5174) +# PORT=5174 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a4e08ae --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# Build static Branding Layer + Signing Layer assets +FROM node:22-alpine AS build + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY index.html vite.config.ts tsconfig.json tsconfig.node.json components.json ./ +COPY src ./src +COPY scripts ./scripts +COPY signer-static ./signer-static +COPY vendor/ows-signer ./vendor/ows-signer + +RUN npm run build + +# Serve with nginx +FROM nginx:1.27-alpine + +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist/wallet /usr/share/nginx/html/wallet +COPY --from=build /app/dist/signer /usr/share/nginx/html/signer + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -qO- http://127.0.0.1/wallet/ >/dev/null || exit 1 diff --git a/README.md b/README.md index 85eabd8..46e537e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,74 @@ # embedded-wallet -1Shot API's free, permissionless embedded wallet. Includes passkey based verification and OIDv4 credential managment for shared KYC + +1Shot API's free, permissionless embedded wallet. Includes passkey-based verification and OID4 credential management for shared KYC. + +This repo is the **OWS Branding Layer** for the 1Shot Wallet — a frontend-only static app (React + Vite + Tailwind + shadcn/ui) that will be hosted at `wallet.1shotapi.com`. + +``` +Host Layer (integrator dapp) + └── This app /wallet/ Branding Layer + └── /signer/ OWS Signing Layer (vendored ows-signer, unbundled) +``` + +## Stack + +| Path | Content | +|------|---------| +| `/wallet/` | React Branding Layer (Vite bundle) | +| `/signer/` | Static `@1shotapi/ows-signer` ES modules | + +Production deliverable: a static **nginx** Docker image (no server-side runtime). + +## Setup + +```bash +npm install +npm run sync:signer # copies ../prf-wallet/packages/ows-signer → vendor/ows-signer +cp .env.example .env # set NGROK_AUTHTOKEN (and optional NGROK_DOMAIN) +``` + +`sync:signer` defaults to sibling `../prf-wallet/packages/ows-signer`. Override with `OWS_SIGNER_SRC`. + +## Develop + +```bash +npm run dev # Vite + ngrok HTTPS tunnel +npm run dev:local # local HTTP only (no tunnel) +``` + +| Service | Local URL | +|---------|-----------| +| Branding (`/wallet/`) | http://localhost:5174/wallet/ | +| Signing (`/signer/`) | http://localhost:5174/signer/ | + +Passkeys need HTTPS — use the printed ngrok `/wallet/` URL as the host iframe source. + +## Build + +```bash +npm run build # dist/wallet + dist/signer +npm run preview # preview production wallet build +``` + +## Docker + +```bash +docker build -t oneshot-wallet . +docker run --rm -p 8080:80 oneshot-wallet +# open http://localhost:8080/wallet/ +# signer at http://localhost:8080/signer/ +``` + +## Scripts + +| Script | Purpose | +|--------|---------| +| `dev` / `dev:local` | Dev server (± ngrok) | +| `build` | Typecheck + Vite build + copy signer | +| `sync:signer` | Refresh vendored Signing Layer | +| `clean` | Remove `dist/` | +| `lint` | `tsc --noEmit` | + +## Status + +Scaffold only — OWS helpers (`OWSWallet`, `SignHelper`, credentials ↔ relayer) not wired yet. diff --git a/components.json b/components.json new file mode 100644 index 0000000..5c23ec4 --- /dev/null +++ b/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "radix-nova", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": {} +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..5ee0887 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + 1Shot Wallet + + +
+ + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..c9da7f4 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,30 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + + # Keep Location headers relative so published host ports (e.g. :8080) survive redirects. + absolute_redirect off; + + # Signing Layer — unbundled ES modules (same origin as branding) + location /signer/ { + try_files $uri $uri/ /signer/index.html; + } + + location = /signer { + return 301 /signer/; + } + + # Branding Layer — Vite SPA under /wallet/ + location /wallet/ { + try_files $uri $uri/ /wallet/index.html; + } + + location = /wallet { + return 301 /wallet/; + } + + location = / { + return 302 /wallet/; + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..175de56 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7789 @@ +{ + "name": "@1shotapi/embedded-wallet", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@1shotapi/embedded-wallet", + "version": "0.0.0", + "dependencies": { + "@fontsource-variable/geist": "^5.2.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.525.0", + "radix-ui": "^1.6.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "shadcn": "^4.13.0", + "tailwind-merge": "^3.3.1" + }, + "devDependencies": { + "@ngrok/ngrok": "^1.7.0", + "@tailwindcss/vite": "^4.1.11", + "@types/node": "^22.15.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "dotenv": "^16.4.7", + "tailwindcss": "^4.1.11", + "tw-animate-css": "^1.3.5", + "typescript": "~5.8.0", + "vite": "^7.0.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dotenvx/dotenvx": { + "version": "1.75.1", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz", + "integrity": "sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@dotenvx/primitives": "^0.8.0", + "commander": "^11.1.0", + "conf": "^10.2.0", + "dotenv": "^17.2.1", + "enquirer": "^2.4.1", + "env-paths": "^2.2.1", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "open": "^8.4.2", + "picomatch": "^4.0.4", + "systeminformation": "^5.22.11", + "undici": "^7.11.0", + "which": "^4.0.0", + "yocto-spinner": "^1.1.0" + }, + "bin": { + "dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@dotenvx/primitives": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@dotenvx/primitives/-/primitives-0.8.0.tgz", + "integrity": "sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==", + "license": "BSD-3-Clause" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@fontsource-variable/geist": { + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.2.9.tgz", + "integrity": "sha512-TP+QSBG3wxKGPE33CbMy/L0Nu3qvJ6Fy81Yc4LnQ95xH+i+cfEp8fyU8/kfV14YwszxIFPhnoMTbjL71waVpyQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@ngrok/ngrok": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok/-/ngrok-1.7.0.tgz", + "integrity": "sha512-P06o9TpxrJbiRbHQkiwy/rUrlXRupc+Z8KT4MiJfmcdWxvIdzjCaJOdnNkcOTs6DMyzIOefG5tvk/HLdtjqr0g==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@ngrok/ngrok-android-arm64": "1.7.0", + "@ngrok/ngrok-darwin-arm64": "1.7.0", + "@ngrok/ngrok-darwin-universal": "1.7.0", + "@ngrok/ngrok-darwin-x64": "1.7.0", + "@ngrok/ngrok-freebsd-x64": "1.7.0", + "@ngrok/ngrok-linux-arm-gnueabihf": "1.7.0", + "@ngrok/ngrok-linux-arm64-gnu": "1.7.0", + "@ngrok/ngrok-linux-arm64-musl": "1.7.0", + "@ngrok/ngrok-linux-x64-gnu": "1.7.0", + "@ngrok/ngrok-linux-x64-musl": "1.7.0", + "@ngrok/ngrok-win32-arm64-msvc": "1.7.0", + "@ngrok/ngrok-win32-ia32-msvc": "1.7.0", + "@ngrok/ngrok-win32-x64-msvc": "1.7.0" + } + }, + "node_modules/@ngrok/ngrok-android-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-android-arm64/-/ngrok-android-arm64-1.7.0.tgz", + "integrity": "sha512-8tco3ID6noSaNy+CMS7ewqPoIkIM6XO5COCzsUp3Wv3XEbMSyn65RN6cflX2JdqLfUCHcMyD0ahr9IEiHwqmbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-arm64/-/ngrok-darwin-arm64-1.7.0.tgz", + "integrity": "sha512-+dmJSOzSO+MNDVrPOca2yYDP1W3KfP4qOlAkarIeFRIfqonQwq3QCBmcR7HAlZocLsSqEwyG6KP4RRvAuT0WGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-universal": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-universal/-/ngrok-darwin-universal-1.7.0.tgz", + "integrity": "sha512-fDEfewyE2pWGFBhOSwQZObeHUkc65U1l+3HIgSOe094TMHsqmyJD0KTCgW9KSn0VP4OvDZbAISi1T3nvqgZYhQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-x64/-/ngrok-darwin-x64-1.7.0.tgz", + "integrity": "sha512-+fwMi5uHd9G8BS42MMa9ye6exI5lwTcjUO6Ut497Vu0qgLONdVRenRqnEePV+Q3KtQR7NjqkMnomVfkr9MBjtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-freebsd-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-freebsd-x64/-/ngrok-freebsd-x64-1.7.0.tgz", + "integrity": "sha512-2OGgbrjy3yLRrqAz5N6hlUKIWIXSpR5RjQa2chtZMsSbszQ6c9dI+uVQfOKAeo05tHMUgrYAZ7FocC+ig0dzdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm-gnueabihf": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm-gnueabihf/-/ngrok-linux-arm-gnueabihf-1.7.0.tgz", + "integrity": "sha512-SN9YIfEQiR9xN90QVNvdgvAemqMLoFVSeTWZs779145hQMhvF9Qd9rnWi6J+2uNNK10OczdV1oc/nq1es7u/3g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-gnu/-/ngrok-linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-KDMgzPKFU2kbpVSaA2RZBBia5IPdJEe063YlyVFnSMJmPYWCUnMwdybBsucXfV9u1Lw/ZjKTKotIlbTWGn3HGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-musl/-/ngrok-linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-e66vUdVrBlQ0lT9ZdamB4U604zt5Gualt8/WVcUGzbu8s5LajWd6g/mzZCUjK4UepjvMpfgmCp1/+rX7Rk8d5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-gnu/-/ngrok-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-M6gF0DyOEFqXLfWxObfL3bxYZ4+PnKBHuyLVaqNfFN9Y5utY2mdPOn5422Ppbk4XoIK5/YkuhRqPJl/9FivKEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-musl/-/ngrok-linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-4Ijm0dKeoyzZTMaYxR2EiNjtlK81ebflg/WYIO1XtleFrVy4UJEGnxtxEidYoT4BfCqi4uvXiK2Mx216xXKvog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-arm64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-arm64-msvc/-/ngrok-win32-arm64-msvc-1.7.0.tgz", + "integrity": "sha512-u7qyWIJI2/YG1HTBnHwUR1+Z2tyGfAsUAItJK/+N1G0FeWJhIWQvSIFJHlaPy4oW1Dc8mSDBX9qvVsiQgLaRFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-ia32-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-ia32-msvc/-/ngrok-win32-ia32-msvc-1.7.0.tgz", + "integrity": "sha512-/UdYUsLNv/Q8j9YJsyIfq/jLCoD8WP+NidouucTUzSoDtmOsXBBT3itLrmPiZTEdEgKiFYLuC1Zon8XQQvbVLA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-x64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-x64-msvc/-/ngrok-win32-x64-msvc-1.7.0.tgz", + "integrity": "sha512-UFJg/duEWzZlLkEs61Gz6/5nYhGaKI62I8dvUGdBR3NCtIMagehnFaFxmnXZldyHmCM8U0aCIFNpWRaKcrQkoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", + "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", + "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.11.tgz", + "integrity": "sha512-HQDOFTKwSnmUij6l54wYJJtxTAnxI71+YJLOrjm2ladFB8HAV5Jt7hwaZPhWTGBkYoW4+ZAOfNZrLDh/qvxSYA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.2.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.16.tgz", + "integrity": "sha512-BpZJNmetujnGgUI6OX0jEhEmlA46WPqgub8Rv09Kyquwd0cc1ndMKpiPYCjmBU6KSSRPAMtgLpEoZSG/tdNIWQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collapsible": "1.1.16", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.19.tgz", + "integrity": "sha512-FA7n1f6D/DwGE0+AWxiY5LacNbbExQuEgMubeG06idEaH+mSLuf9dp/qBNqOnvbTQ+4gZ2ue1RATF1Ub91Mg5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-dialog": "1.1.19", + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.11.tgz", + "integrity": "sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.11.tgz", + "integrity": "sha512-IUAhIVpBUvP5NNICjlaB1OFmtRLGqQqTF3ZOSGPoq3XeLXRFtHiWTRxSVEULgOd9GQR2c7tsYqDnhUennapZnw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.2.tgz", + "integrity": "sha512-sST0qh8GzOB7besQ3tMLWLyngnRuSk0gc/Hm+667KYKQFCt6Y6ZXv25WlqM7dIDK54ULCh5+CHmk4LIolzfz+A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.7.tgz", + "integrity": "sha512-JroKHfQBfh+fDuzpPsBC+pESkhuq8ql4hljTguz8MWnS35cISr3d/Jhl9kYrB44FlDtxCArYdDvTx+BSsJ64rQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.16.tgz", + "integrity": "sha512-opfXRe6nnzyGmCDPx+l1Aqo/RbqWtQal2FnsBqF9hhePp6j0LsRoBaRxcMOlTv+uYTJVtWYZKg9t9wTe+BA/ZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", + "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", + "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.3.tgz", + "integrity": "sha512-PS+gKE0z2prJ74Y0sM+brAGK4mYOHIR7TlcV5EJgUQ6E0xMvyswkK2X4yRqyganrzsRL+WCSKAPu0NQITICRWg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-menu": "2.1.20", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.19.tgz", + "integrity": "sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.12", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", + "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz", + "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-effect-event": "0.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.20.tgz", + "integrity": "sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-menu": "2.1.20", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", + "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz", + "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.12.tgz", + "integrity": "sha512-JTX94E4LDL91rzLg7X0mHPdxr0A8JEdVwZEmeOwZJSMDHCGW5DFtSlTSJozUyUs807IQmnvbfzKZFVCK5DmkqQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-label": "2.1.11", + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.19.tgz", + "integrity": "sha512-2KTgMLQtKvicznQgbindEI2RZ3QbDIwU5gabjUPwFJsormjGDz+rUvO4NANmYwzEEpTcTONUt33vBHIfTIVSfw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-popper": "1.3.3", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.11.tgz", + "integrity": "sha512-3PKvDDxOn62k0oV1n4QtNtD2vpu+zYjXR7ojLBPaO6SPvhy53yg0vAmgNeBQeJW5rV3dffoRG+HYfLBZuzw0CQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.20.tgz", + "integrity": "sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.12", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.3", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-callback-ref": "1.1.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.20.tgz", + "integrity": "sha512-gzFZvybgmwYsFBWDqanycIoEYnhyk8MMnuLamdFVHUZYGp4COM+sqXiwbnn0VMWqGLeeU7GV7jm+dXRa+Wufag==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-menu": "2.1.20", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.18.tgz", + "integrity": "sha512-K9HiuxZ6xCwSaHcIuUpxyhy4w5gpwzWjh9dHTSbMN3Ix4qAyVObS9RlU3zMycb0PO3v9Tpk0BXMwWvXOUbVXew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.12.tgz", + "integrity": "sha512-nQLu5OAcORDQp1EHAv6k3mJGV1hjMTw2NTGVAsGE1g/mWeNqAd1R5jyaAs3U+A8ZD/W8XNPY2yKT0ZdQnqo3NA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.7.tgz", + "integrity": "sha512-gB1Mr8vzdv1XzDjrtJTXmL0JORRs1B4g7ngUs0F+H2VvMOwXTZMTmLCl0wZZ3m7ylX8TssI7NCvgiSHmLuTm/A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-is-hydrated": "0.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.19.tgz", + "integrity": "sha512-jkrTdQVxnIB8fpn0NyyxW9CTB5aCXZZelVz5z+Xmii6g5WxMqS3fInNslZ63puP39+Puu4jYohUK31y3dT87gQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.12", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.3", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.3.tgz", + "integrity": "sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.11", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-rect": "1.1.2", + "@radix-ui/react-use-size": "1.1.2", + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz", + "integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", + "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", + "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.12.tgz", + "integrity": "sha512-ZPHyI0JyzoH/rP0tq2uRaIZTj/4s8+kAbqPz+e2N8+ejHvwPJ889dHhqn+vh7PNvNeq+boAoH9yzqeoShzwF2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.3.tgz", + "integrity": "sha512-WwZFjWV4s3aC1QtR3k04R+oANHtX2q6fgKlc7MCEiDNlnTxCZ3H8k3mHtEgVlOejystwk1WQgarQhNOQZ2bK1g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz", + "integrity": "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.14.tgz", + "integrity": "sha512-bBODCWZK7JTbQLHs0uIP4f73wIWatakK4OS33UzkR1x897wu0PuO658a3f+6P2GEGyDzGYMuHRatMVoAk9WZTw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.3.tgz", + "integrity": "sha512-L5RQTXz6Anxsf9CCv+pTgiAsUpyVj7rJxsGtmhFaEOJ++cVfXucv4qWfsIO0AIB4NAhi3yovWGVMKKS1Xf1Wrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.12", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.3", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.7", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.11.tgz", + "integrity": "sha512-jRhe86+8PF7VZ1u14eOWVOuh2BuAhALg/FT1VcMC4OHedMTRUazDnDlKTt+yxo5cRNKHMfmvZ4sSQtWDeMV4CQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.3.tgz", + "integrity": "sha512-CWVVj+XaTom0SKCqw1EUgb0NuiLwS+N3OFG73mVEezKEjgNIvZiu0EevMelSSU+CbX3owbqJweG2gPU31WGC5A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", + "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.3.tgz", + "integrity": "sha512-1+mlB4/lxJfk5tgJ4g+R5mUCbRpPE1T9+UsEyeLYbGgMtwiMgmuTnfKz4Mw1nHALHjuwyxw4MLd4cSHn6pNSlQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.17.tgz", + "integrity": "sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.19.tgz", + "integrity": "sha512-SxfVZfVOibWKWdkf0Xx1awW2d09fQu4V4PXDY1j5hi4MVf7MWdJZqTBJMa1KWtOr1S6GGtCk02nniZ0Iia+dHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.14.tgz", + "integrity": "sha512-QI/hB65XKWACA66P64A+aHxtLUgHJeJLkaQa+awUNXT6T3swndtY5DojeHA+vldrTspMTtFBd7HfZ9QGbM1Qrw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.15.tgz", + "integrity": "sha512-gIC5Q+Xljg7lmUdzSuDoy0t97yZn1sZl00Ra37ZvKrYdWnQLU6sWLd09yG8cIB9jUAlQfHgJ2ACAG00MFwsqSQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-toggle": "1.1.14", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.15.tgz", + "integrity": "sha512-t/iEuVjUnXXtrsGK40AA43uIx37sn3AqZ7oAVnPICK6lFJP6dzMzWR3U9b6eCfFjb6wtSEqkJ9Rn9xDjiOx20g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-separator": "1.1.11", + "@radix-ui/react-toggle-group": "1.1.15" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.12.tgz", + "integrity": "sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.3", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-visually-hidden": "1.2.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.3.tgz", + "integrity": "sha512-3wEkMiPHXha/2VadZ68rYBcmYnPINVGl4Y3gtcM7fKRjANk0OscK+cdqBgUWdozb7YJxsh0vefM7vgAMHXOjqg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz", + "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz", + "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", + "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", + "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.7.tgz", + "integrity": "sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", + "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", + "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", + "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-x64": "4.3.2", + "@tailwindcss/oxide-freebsd-x64": "4.3.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-x64-musl": "4.3.2", + "@tailwindcss/oxide-wasm32-wasi": "4.3.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", + "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", + "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", + "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", + "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", + "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", + "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", + "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", + "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", + "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", + "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", + "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", + "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", + "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.2", + "@tailwindcss/oxide": "4.3.2", + "tailwindcss": "4.3.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/validate-npm-package-name": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", + "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "license": "MIT", + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/conf": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "license": "MIT", + "dependencies": { + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "json-schema-typed": "^7.0.3", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/conf/node_modules/json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", + "license": "BSD-2-Clause" + }, + "node_modules/conf/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.391", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.391.tgz", + "integrity": "sha512-YmCu4856jkgKT1Nh6fwRdeVrM6Ydf/fBnq51tpmSfX+jOcUMTxh31yH6hjKScRenhB2oDSvA9oooxcpjogPeig==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuzzysort": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "license": "MIT" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.30", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.30.tgz", + "integrity": "sha512-emn+JoJjrN9YTpRDS5it/UI2SO9BAE37T6I3d963RxcZ81G9A4pr2SZTEiiaiKbzx+NKRg5BZ89fCL7gCJCUog==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.525.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz", + "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix-ui": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.2.tgz", + "integrity": "sha512-OwYUjzMwiInCUxgAWpPsavXC3Kh4iyi/49uU1/qZTG3RQDlvegyk1GOMiGvSkjua1RDb3JD3fo3eroL9FV4GQw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-accessible-icon": "1.1.11", + "@radix-ui/react-accordion": "1.2.16", + "@radix-ui/react-alert-dialog": "1.1.19", + "@radix-ui/react-arrow": "1.1.11", + "@radix-ui/react-aspect-ratio": "1.1.11", + "@radix-ui/react-avatar": "1.2.2", + "@radix-ui/react-checkbox": "1.3.7", + "@radix-ui/react-collapsible": "1.1.16", + "@radix-ui/react-collection": "1.1.12", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-context-menu": "2.3.3", + "@radix-ui/react-dialog": "1.1.19", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.15", + "@radix-ui/react-dropdown-menu": "2.1.20", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.12", + "@radix-ui/react-form": "0.1.12", + "@radix-ui/react-hover-card": "1.1.19", + "@radix-ui/react-label": "2.1.11", + "@radix-ui/react-menu": "2.1.20", + "@radix-ui/react-menubar": "1.1.20", + "@radix-ui/react-navigation-menu": "1.2.18", + "@radix-ui/react-one-time-password-field": "0.1.12", + "@radix-ui/react-password-toggle-field": "0.1.7", + "@radix-ui/react-popover": "1.1.19", + "@radix-ui/react-popper": "1.3.3", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.7", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-progress": "1.1.12", + "@radix-ui/react-radio-group": "1.4.3", + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-scroll-area": "1.2.14", + "@radix-ui/react-select": "2.3.3", + "@radix-ui/react-separator": "1.1.11", + "@radix-ui/react-slider": "1.4.3", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-switch": "1.3.3", + "@radix-ui/react-tabs": "1.1.17", + "@radix-ui/react-toast": "1.2.19", + "@radix-ui/react-toggle": "1.1.14", + "@radix-ui/react-toggle-group": "1.1.15", + "@radix-ui/react-toolbar": "1.1.15", + "@radix-ui/react-tooltip": "1.2.12", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-escape-keydown": "1.1.3", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-size": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/recast": { + "version": "0.23.12", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.12.tgz", + "integrity": "sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shadcn": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.13.0.tgz", + "integrity": "sha512-5fuJ4jI/GcPeA/iTL4cJivCZuYQGXz/N3bIzyd+Gd/FM6xUCy2MxGG+LaDQuw2cjNy9zGPSFPTEmI048UwPTZA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/plugin-transform-typescript": "^7.28.0", + "@babel/preset-typescript": "^7.27.1", + "@dotenvx/dotenvx": "^1.48.4", + "@modelcontextprotocol/sdk": "^1.26.0", + "@types/validate-npm-package-name": "^4.0.2", + "browserslist": "^4.26.2", + "commander": "^14.0.0", + "cosmiconfig": "^9.0.0", + "dedent": "^1.6.0", + "deepmerge": "^4.3.1", + "diff": "^8.0.2", + "execa": "^9.6.0", + "fast-glob": "^3.3.3", + "fs-extra": "^11.3.1", + "fuzzysort": "^3.1.0", + "kleur": "^4.1.5", + "open": "^11.0.0", + "ora": "^8.2.0", + "postcss": "^8.5.6", + "postcss-selector-parser": "^7.1.0", + "prompts": "^2.4.2", + "recast": "^0.23.11", + "stringify-object": "^5.0.0", + "tailwind-merge": "^3.0.1", + "ts-morph": "^26.0.0", + "tsconfig-paths": "^4.2.0", + "undici": "^7.27.2", + "validate-npm-package-name": "^7.0.1", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "shadcn": "dist/index.js" + }, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stringify-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-keys": "^1.0.0", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/stringify-object?sponsor=1" + } + }, + "node_modules/stringify-object/node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/systeminformation": { + "version": "5.31.17", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.17.tgz", + "integrity": "sha512-TvFA9iwDWlMjqZVlKIJ0Cy+Zgm9ttlMx0SMRwJDMNKyhlEKWBMb3+WRwDi/3dvHdWbexpos4Osp4U49p5WjB5g==", + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", + "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-morph": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", + "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.27.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yocto-spinner": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.1.tgz", + "integrity": "sha512-9cbFWLhbiZp+820O4pkHGNncI7+MrUGzBOjw8NMG+ewsY+aG0DdEXnr19Smxao32YOjLZRMdn1UtaxcrXOYOIg==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..38aa3a2 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "@1shotapi/embedded-wallet", + "version": "0.0.0", + "private": true, + "description": "1Shot Wallet — OWS Branding Layer (React + Vite + Tailwind)", + "type": "module", + "scripts": { + "dev": "node scripts/dev.mjs", + "dev:local": "node scripts/dev.mjs --no-tunnel", + "build": "tsc -p tsconfig.json --noEmit && vite build && node scripts/copy-signer.mjs", + "sync:signer": "node scripts/sync-signer.mjs", + "clean": "node scripts/clean.mjs", + "lint": "tsc -p tsconfig.json --noEmit", + "preview": "vite preview", + "dockerize": "docker build -t oneshot-wallet ." + }, + "dependencies": { + "@fontsource-variable/geist": "^5.2.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.525.0", + "radix-ui": "^1.6.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "shadcn": "^4.13.0", + "tailwind-merge": "^3.3.1" + }, + "devDependencies": { + "@ngrok/ngrok": "^1.7.0", + "@tailwindcss/vite": "^4.1.11", + "@types/node": "^22.15.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "dotenv": "^16.4.7", + "tailwindcss": "^4.1.11", + "tw-animate-css": "^1.3.5", + "typescript": "~5.8.0", + "vite": "^7.0.4" + } +} diff --git a/scripts/clean.mjs b/scripts/clean.mjs new file mode 100644 index 0000000..26d0615 --- /dev/null +++ b/scripts/clean.mjs @@ -0,0 +1,8 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const dist = path.resolve(__dirname, "../dist"); +fs.rmSync(dist, { recursive: true, force: true }); +console.log("Removed dist/"); diff --git a/scripts/copy-signer.mjs b/scripts/copy-signer.mjs new file mode 100644 index 0000000..b30790d --- /dev/null +++ b/scripts/copy-signer.mjs @@ -0,0 +1,23 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, ".."); +const vendorSignerSrc = path.join(repoRoot, "vendor/ows-signer/src"); +const signerPublic = path.join(repoRoot, "signer-static/index.html"); +const outSigner = path.join(repoRoot, "dist/signer"); + +if (!fs.existsSync(vendorSignerSrc)) { + console.error( + "vendor/ows-signer/src missing. Run: npm run sync:signer", + ); + process.exit(1); +} + +fs.rmSync(outSigner, { recursive: true, force: true }); +fs.mkdirSync(path.join(outSigner, "src"), { recursive: true }); +fs.cpSync(vendorSignerSrc, path.join(outSigner, "src"), { recursive: true }); +fs.copyFileSync(signerPublic, path.join(outSigner, "index.html")); + +console.log("Copied Signing Layer to dist/signer/"); diff --git a/scripts/dev.mjs b/scripts/dev.mjs new file mode 100644 index 0000000..6218412 --- /dev/null +++ b/scripts/dev.mjs @@ -0,0 +1,141 @@ +/** + * Start Vite for Branding Layer + Signing Layer, optionally expose via ngrok. + * Loads NGROK_AUTHTOKEN from repo root .env (copy from .env.example). + * + * Usage: + * node scripts/dev.mjs # dev server + ngrok tunnel + * node scripts/dev.mjs --no-tunnel # local HTTP only + */ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createServer } from "vite"; +import dotenv from "dotenv"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, ".."); +dotenv.config({ path: path.join(repoRoot, ".env") }); + +const preferredPort = Number(process.env.PORT ?? 5174); +const noTunnel = process.argv.includes("--no-tunnel"); +let viteServer; +let ngrokListener; +let shuttingDown = false; +/** Port Vite actually bound (must match ngrok `addr`). */ +let listenPort = preferredPort; + +async function startDevServer() { + viteServer = await createServer({ + configFile: path.join(__dirname, "../vite.config.ts"), + server: { + port: preferredPort, + strictPort: true, + host: "0.0.0.0", + }, + }); + await viteServer.listen(); + + const address = viteServer.httpServer?.address(); + if (address && typeof address === "object") { + listenPort = address.port; + } else { + listenPort = preferredPort; + } +} + +async function startTunnel() { + const ngrok = (await import("@ngrok/ngrok")).default; + const domain = normalizeNgrokDomain(process.env.NGROK_DOMAIN); + const options = { + addr: listenPort, + authtoken_from_env: true, + }; + if (domain) { + options.domain = domain; + } + ngrokListener = await ngrok.forward(options); + return ngrokListener.url(); +} + +/** Hostname only — accepts `immune-sheep-light.ngrok-free.app` or a full URL. */ +function normalizeNgrokDomain(value) { + const raw = value?.trim(); + if (!raw) return undefined; + try { + const url = raw.includes("://") ? raw : `https://${raw}`; + return new URL(url).hostname; + } catch { + return raw.replace(/^https?:\/\//, "").replace(/\/+$/, ""); + } +} + +function printUrls(tunnelUrl) { + const localWallet = `http://localhost:${listenPort}/wallet/`; + const localSigner = `http://localhost:${listenPort}/signer/`; + + console.log(`1Shot Wallet dev server: http://localhost:${listenPort}`); + console.log(` Branding Layer (local): ${localWallet}`); + console.log(` Signing Layer (local): ${localSigner}`); + + if (tunnelUrl) { + const walletUrl = new URL("/wallet/", tunnelUrl).href; + const signerUrl = new URL("/signer/", tunnelUrl).href; + console.log(` Branding Layer (ngrok): ${walletUrl}`); + console.log(` Signing Layer (ngrok): ${signerUrl}`); + console.log(` Host env: WALLET_IFRAME_URL=${walletUrl}`); + } +} + +async function shutdown(signal) { + if (shuttingDown) return; + shuttingDown = true; + + try { + if (ngrokListener) { + await ngrokListener.close(); + } + if (viteServer) { + await viteServer.close(); + } + } catch (error) { + console.error(`Failed to shut down (${signal}):`, error); + process.exit(1); + return; + } + + process.exit(0); +} + +process.on("SIGINT", () => { + void shutdown("SIGINT"); +}); +process.on("SIGTERM", () => { + void shutdown("SIGTERM"); +}); + +try { + await startDevServer(); + + let tunnelUrl; + if (!noTunnel) { + if (!process.env.NGROK_AUTHTOKEN) { + console.warn( + "NGROK_AUTHTOKEN not set — running local only. Copy .env.example to .env at repo root.", + ); + } else { + tunnelUrl = await startTunnel(); + } + } + + printUrls(tunnelUrl); +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + if (/Port .* is already in use|EADDRINUSE/i.test(message)) { + console.error( + `Port ${preferredPort} is already in use. Stop the other wallet process ` + + `(or anything on that port), then retry. A leftover \`dev:local\` / ` + + `\`--no-tunnel\` process will steal ngrok traffic and break the host handshake.`, + ); + } + console.error("Failed to start wallet dev server:", error); + process.exit(1); +} diff --git a/scripts/sync-signer.mjs b/scripts/sync-signer.mjs new file mode 100644 index 0000000..9c12195 --- /dev/null +++ b/scripts/sync-signer.mjs @@ -0,0 +1,37 @@ +/** + * Refresh vendored @1shotapi/ows-signer from a local open-wallet / prf-wallet clone. + * + * Default source: ../prf-wallet/packages/ows-signer + * Override: OWS_SIGNER_SRC=/path/to/packages/ows-signer + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, ".."); +const defaultSrc = path.resolve(repoRoot, "../prf-wallet/packages/ows-signer"); +const srcRoot = process.env.OWS_SIGNER_SRC + ? path.resolve(process.env.OWS_SIGNER_SRC) + : defaultSrc; +const destRoot = path.join(repoRoot, "vendor/ows-signer"); +const srcDir = path.join(srcRoot, "src"); + +if (!fs.existsSync(srcDir)) { + console.error( + `ows-signer source not found at ${srcDir}\n` + + `Set OWS_SIGNER_SRC to packages/ows-signer (default: ${defaultSrc})`, + ); + process.exit(1); +} + +fs.rmSync(destRoot, { recursive: true, force: true }); +fs.mkdirSync(destRoot, { recursive: true }); +fs.cpSync(srcDir, path.join(destRoot, "src"), { recursive: true }); + +const pkgJson = path.join(srcRoot, "package.json"); +if (fs.existsSync(pkgJson)) { + fs.copyFileSync(pkgJson, path.join(destRoot, "package.json")); +} + +console.log(`Synced ows-signer src → vendor/ows-signer/ (from ${srcRoot})`); diff --git a/signer-static/index.html b/signer-static/index.html new file mode 100644 index 0000000..d7fdea8 --- /dev/null +++ b/signer-static/index.html @@ -0,0 +1,54 @@ + + + + + + OWS Custody Signer + + + +
+ + + diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..f89bf95 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "skills": { + "ows-branding-layer": { + "source": "C:\\Users\\charl\\Source\\prf-wallet", + "sourceType": "local", + "computedHash": "b6e8aadef89264b05a76c525163272bebbadda53e64f005994fa742ea4d42063" + } + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..0f3f563 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,13 @@ +import { Button } from "@/components/ui/button"; + +export function App() { + return ( +
+

1Shot Wallet

+

+ Branding Layer bootstrapping — OWS wiring comes next. +

+ +
+ ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..75b8c3d --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,67 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/80", + outline: + "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: + "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + icon: "size-8", + "icon-xs": + "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", + "icon-sm": + "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg", + "icon-lg": "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot.Root : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..fb3c7e9 --- /dev/null +++ b/src/index.css @@ -0,0 +1,130 @@ +@import "tailwindcss"; +@import "tw-animate-css"; +@import "shadcn/tailwind.css"; +@import "@fontsource-variable/geist"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --font-heading: var(--font-sans); + --font-sans: 'Geist Variable', sans-serif; + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); +} + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + html { + @apply font-sans; + } +} \ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..d4824f1 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import { createRoot } from "react-dom/client"; +import { App } from "./App"; +import "./index.css"; + +const root = document.getElementById("root"); +if (!root) { + throw new Error("#root not found"); +} + +createRoot(root).render(); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c6eb967 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "types": ["vite/client"], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*", "vite.config.ts"] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..7effa0a --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "strict": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["vite.config.ts", "scripts/**/*"] +} diff --git a/vendor/ows-signer/package.json b/vendor/ows-signer/package.json new file mode 100644 index 0000000..b811e87 --- /dev/null +++ b/vendor/ows-signer/package.json @@ -0,0 +1,37 @@ +{ + "name": "@1shotapi/ows-signer", + "version": "0.1.0", + "description": "OWS Signing Layer — plain JavaScript WebAuthn PRF key custody with curve-based signing (on-chain via EIP-8244)", + "type": "module", + "files": [ + "src", + "html", + "README.md" + ], + "exports": { + "./html": "./html/index.html", + "./src/*": "./src/*" + }, + "scripts": { + "test": "node --test test/*.test.js" + }, + "keywords": [ + "ows", + "open-wallet-standard", + "webauthn", + "prf", + "passkey", + "signer", + "custody", + "secp256k1" + ], + "repository": { + "type": "git", + "url": "https://github.com/1Shot-API/open-wallet.git", + "directory": "packages/ows-signer" + }, + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/vendor/ows-signer/src/constants.js b/vendor/ows-signer/src/constants.js new file mode 100644 index 0000000..d5bc1d7 --- /dev/null +++ b/vendor/ows-signer/src/constants.js @@ -0,0 +1,30 @@ +/** @typedef {'secp256k1-ecdsa' | 'secp256k1-ecdsa-recoverable' | 'secp256k1-bip340' | 'ed25519'} SignScheme */ + +export const API_VERSION = 1; +export const SIGNER_VERSION = "0.1.0"; + +export const PRF_LABEL_SECP256K1 = new TextEncoder().encode("ows-v1/secp256k1"); +export const PRF_LABEL_ED25519 = new TextEncoder().encode("ows-v1/ed25519"); +/** HKDF info for AES-256-GCM — IKM is the secp256k1 scalar (`signDigest` material). */ +export const PRF_LABEL_AES256 = new TextEncoder().encode("ows-v1/aes256-gcm"); + +/** @type {SignScheme[]} */ +export const SIGN_SCHEMES = [ + "secp256k1-ecdsa", + "secp256k1-ecdsa-recoverable", + "secp256k1-bip340", + "ed25519", +]; + +export const METHODS = [ + "getVersion", + "createCredential", + "signDigest", + "revealPrivateKey", + "createRecoveryData", + "recoverKey", + "getPublicKey", + "clearRecoverySession", + "encryptAES256", + "decryptAES256", +]; diff --git a/vendor/ows-signer/src/crypto/aes256.js b/vendor/ows-signer/src/crypto/aes256.js new file mode 100644 index 0000000..5df1c48 --- /dev/null +++ b/vendor/ows-signer/src/crypto/aes256.js @@ -0,0 +1,115 @@ +import { PRF_LABEL_AES256 } from "../constants.js"; +import { parse0xHex, to0xHex } from "../hex.js"; +import { hkdfExpand } from "./prf.js"; + +/** Envelope prefix — distinct from recovery `ows1:` (PBKDF2). */ +export const AES256_ENVELOPE_PREFIX = "ows-aes1:"; +const AES256_VERSION = 1; +const IV_LENGTH = 12; + +/** + * Derive an AES-256-GCM CryptoKey from the wallet secp256k1 scalar + * (same material `signDigest` uses), via HKDF domain separation. + * + * @param {Uint8Array} secp256k1PrivateKey + * @returns {Promise} + */ +export async function deriveAes256KeyFromSecp256k1(secp256k1PrivateKey) { + const raw = await hkdfExpand( + secp256k1PrivateKey.buffer.slice( + secp256k1PrivateKey.byteOffset, + secp256k1PrivateKey.byteOffset + secp256k1PrivateKey.byteLength, + ), + PRF_LABEL_AES256, + 32, + ); + try { + return await crypto.subtle.importKey( + "raw", + raw, + { name: "AES-GCM", length: 256 }, + false, + ["encrypt", "decrypt"], + ); + } finally { + raw.fill(0); + } +} + +/** + * Encrypt a UTF-8 plaintext. Envelope: `ows-aes1:0x` ‖ version ‖ IV ‖ ciphertext+tag. + * + * @param {string} plaintext + * @param {CryptoKey} aesKey + * @returns {Promise} + */ +export async function encryptAes256String(plaintext, aesKey) { + const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)); + const encoded = new TextEncoder().encode(plaintext); + const ciphertext = new Uint8Array( + await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, encoded), + ); + const payload = new Uint8Array(1 + IV_LENGTH + ciphertext.length); + payload[0] = AES256_VERSION; + payload.set(iv, 1); + payload.set(ciphertext, 1 + IV_LENGTH); + return `${AES256_ENVELOPE_PREFIX}${to0xHex(payload)}`; +} + +/** + * Decrypt an `ows-aes1:` envelope to a UTF-8 string. + * + * @param {string} envelope + * @param {CryptoKey} aesKey + * @returns {Promise} + */ +export async function decryptAes256String(envelope, aesKey) { + if (!envelope.startsWith(AES256_ENVELOPE_PREFIX)) { + throw new Error("invalidAes256Envelope"); + } + const bytes = parse0xHex(envelope.slice(AES256_ENVELOPE_PREFIX.length)); + if (bytes.length < 1 + IV_LENGTH + 16) { + throw new Error("invalidAes256Envelope"); + } + if (bytes[0] !== AES256_VERSION) { + throw new Error("unsupportedAes256Version"); + } + const iv = bytes.slice(1, 1 + IV_LENGTH); + const ciphertext = bytes.slice(1 + IV_LENGTH); + try { + const plain = new Uint8Array( + await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, ciphertext), + ); + return new TextDecoder().decode(plain); + } catch { + throw new Error("decryptionFailed"); + } +} + +/** + * @param {string[]} plaintexts + * @param {Uint8Array} secp256k1PrivateKey + * @returns {Promise} + */ +export async function encryptAes256Batch(plaintexts, secp256k1PrivateKey) { + const aesKey = await deriveAes256KeyFromSecp256k1(secp256k1PrivateKey); + const out = []; + for (const plaintext of plaintexts) { + out.push(await encryptAes256String(plaintext, aesKey)); + } + return out; +} + +/** + * @param {string[]} envelopes + * @param {Uint8Array} secp256k1PrivateKey + * @returns {Promise} + */ +export async function decryptAes256Batch(envelopes, secp256k1PrivateKey) { + const aesKey = await deriveAes256KeyFromSecp256k1(secp256k1PrivateKey); + const out = []; + for (const envelope of envelopes) { + out.push(await decryptAes256String(envelope, aesKey)); + } + return out; +} diff --git a/vendor/ows-signer/src/crypto/prf.js b/vendor/ows-signer/src/crypto/prf.js new file mode 100644 index 0000000..b3b477f --- /dev/null +++ b/vendor/ows-signer/src/crypto/prf.js @@ -0,0 +1,119 @@ +import { PRF_LABEL_ED25519, PRF_LABEL_SECP256K1 } from "../constants.js"; +import { getPublicKeyAsync as edGetPublicKeyAsync } from "./vendor/noble-ed25519.js"; +import { getPublicKey as secpGetPublicKey } from "./vendor/noble-secp256k1.js"; + +const SECP256K1_N = BigInt( + "0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", +); + +/** + * @param {unknown} prfOutput + * @returns {ArrayBuffer | null} + */ +export function normalizePrfOutputToArrayBuffer(prfOutput) { + if (prfOutput instanceof ArrayBuffer) return prfOutput; + if (prfOutput instanceof Uint8Array) { + return prfOutput.buffer.slice( + prfOutput.byteOffset, + prfOutput.byteOffset + prfOutput.byteLength, + ); + } + if (ArrayBuffer.isView(prfOutput)) { + const view = /** @type {ArrayBufferView} */ (prfOutput); + return view.buffer.slice( + view.byteOffset, + view.byteOffset + view.byteLength, + ); + } + if (Array.isArray(prfOutput)) { + return new Uint8Array(prfOutput).buffer; + } + return null; +} + +/** + * @param {ArrayBuffer} prfBuffer + * @param {Uint8Array} infoLabel + * @param {number} length + * @returns {Promise} + */ +export async function hkdfExpand(prfBuffer, infoLabel, length) { + const ikm = new Uint8Array(prfBuffer); + const baseKey = await crypto.subtle.importKey("raw", ikm, "HKDF", false, [ + "deriveBits", + ]); + const salt = new Uint8Array(32); + const bits = await crypto.subtle.deriveBits( + { + name: "HKDF", + hash: "SHA-256", + salt, + info: infoLabel, + }, + baseKey, + length * 8, + ); + return new Uint8Array(bits); +} + +/** + * @param {ArrayBuffer} prfBuffer + * @param {Uint8Array} infoLabel + * @returns {Promise} + */ +export async function deriveScalarFromPrf(prfBuffer, infoLabel) { + for (let counter = 0; counter < 255; counter++) { + const info = new Uint8Array(infoLabel.length + 1); + info.set(infoLabel); + info[infoLabel.length] = counter; + const candidate = await hkdfExpand(prfBuffer, info, 32); + const scalar = bytesToBigInt(candidate); + if (scalar > 0n && scalar < SECP256K1_N) { + return candidate; + } + } + throw new Error("Failed to derive valid secp256k1 scalar from PRF"); +} + +/** + * @param {ArrayBuffer} prfBuffer + * @returns {Promise} + */ +export async function deriveEd25519SeedFromPrf(prfBuffer) { + return hkdfExpand(prfBuffer, PRF_LABEL_ED25519, 32); +} + +/** + * @param {PublicKeyCredential} credential + * @returns {Promise<{ secp256k1PrivateKey: Uint8Array, secp256k1PublicKey: Uint8Array, ed25519PublicKey: Uint8Array }>} + */ +export async function deriveKeysFromCredential(credential) { + const rawPrf = /** @type {{ prf?: { results?: { first?: unknown } } }} */ ( + credential.getClientExtensionResults() + ).prf?.results?.first; + const prfBuffer = normalizePrfOutputToArrayBuffer(rawPrf); + if (!prfBuffer) { + throw new Error("Passkey does not support PRF or PRF output missing"); + } + + const secp256k1PrivateKey = await deriveScalarFromPrf( + prfBuffer, + PRF_LABEL_SECP256K1, + ); + const ed25519Seed = await deriveEd25519SeedFromPrf(prfBuffer); + // Uncompressed (0x04 ‖ X ‖ Y) — required by viem `publicKeyToAddress`. + const secp256k1PublicKey = secpGetPublicKey(secp256k1PrivateKey, false); + const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed); + + return { secp256k1PrivateKey, secp256k1PublicKey, ed25519PublicKey }; +} + +/** + * @param {Uint8Array} bytes + * @returns {bigint} + */ +function bytesToBigInt(bytes) { + let result = 0n; + for (const b of bytes) result = (result << 8n) + BigInt(b); + return result; +} diff --git a/vendor/ows-signer/src/crypto/recovery.js b/vendor/ows-signer/src/crypto/recovery.js new file mode 100644 index 0000000..d68f5c2 --- /dev/null +++ b/vendor/ows-signer/src/crypto/recovery.js @@ -0,0 +1,81 @@ +import { parse0xHex, to0xHex } from "../hex.js"; + +const RECOVERY_VERSION = 1; +const PBKDF2_ITERATIONS = 250_000; + +/** + * @param {string} passphrase + * @param {Uint8Array} salt + * @returns {Promise} + */ +async function deriveAesKey(passphrase, salt) { + const enc = new TextEncoder(); + const keyMaterial = await crypto.subtle.importKey( + "raw", + enc.encode(passphrase), + "PBKDF2", + false, + ["deriveKey"], + ); + return crypto.subtle.deriveKey( + { + name: "PBKDF2", + salt, + iterations: PBKDF2_ITERATIONS, + hash: "SHA-256", + }, + keyMaterial, + { name: "AES-GCM", length: 256 }, + false, + ["encrypt", "decrypt"], + ); +} + +/** + * @param {Uint8Array} privateKey + * @param {string} passphrase + * @returns {Promise} + */ +export async function encryptPrivateKey(privateKey, passphrase) { + const salt = crypto.getRandomValues(new Uint8Array(16)); + const iv = crypto.getRandomValues(new Uint8Array(12)); + const aesKey = await deriveAesKey(passphrase, salt); + const ciphertext = new Uint8Array( + await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, privateKey), + ); + const payload = new Uint8Array( + 1 + salt.length + iv.length + ciphertext.length, + ); + payload[0] = RECOVERY_VERSION; + payload.set(salt, 1); + payload.set(iv, 1 + salt.length); + payload.set(ciphertext, 1 + salt.length + iv.length); + return `ows1:${to0xHex(payload)}`; +} + +/** + * @param {string} envelope + * @returns {Promise} + */ +export async function decryptPrivateKey(envelope, passphrase) { + if (!envelope.startsWith("ows1:")) { + throw new Error("invalidRecoveryEnvelope"); + } + const bytes = parse0xHex(envelope.slice(5)); + if (bytes[0] !== RECOVERY_VERSION) { + throw new Error("unsupportedRecoveryVersion"); + } + const salt = bytes.slice(1, 17); + const iv = bytes.slice(17, 29); + const ciphertext = bytes.slice(29); + const aesKey = await deriveAesKey(passphrase, salt); + try { + const plain = new Uint8Array( + await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, ciphertext), + ); + if (plain.length !== 32) throw new Error("invalidDecryptedKey"); + return plain; + } catch { + throw new Error("decryptionFailed"); + } +} diff --git a/vendor/ows-signer/src/crypto/sign.js b/vendor/ows-signer/src/crypto/sign.js new file mode 100644 index 0000000..92b6f2f --- /dev/null +++ b/vendor/ows-signer/src/crypto/sign.js @@ -0,0 +1,59 @@ +import { to0xHex } from "../hex.js"; +import { signAsync as edSignAsync } from "./vendor/noble-ed25519.js"; +import { signAsync as secpSignAsync } from "./vendor/noble-secp256k1.js"; + +/** + * @param {import('../constants.js').SignScheme} scheme + * @param {Uint8Array} digest + * @param {Uint8Array} secp256k1PrivateKey + * @param {Uint8Array} ed25519Seed + * @returns {Promise<`0x${string}`>} + */ +export async function signWithScheme( + scheme, + digest, + secp256k1PrivateKey, + ed25519Seed, +) { + switch (scheme) { + case "secp256k1-ecdsa": { + const sig = await secpSignAsync(digest, secp256k1PrivateKey, { + lowS: true, + }); + return to0xHex(sig.toCompactRawBytes()); + } + case "secp256k1-ecdsa-recoverable": { + const sig = await secpSignAsync(digest, secp256k1PrivateKey, { + lowS: true, + }); + const compact = sig.toCompactRawBytes(); + const recovery = sig.recovery ?? 0; + const out = new Uint8Array(65); + out.set(compact, 0); + out[64] = recovery; + return to0xHex(out); + } + case "secp256k1-bip340": + throw new Error("schemeNotImplemented"); + case "ed25519": { + const sig = await edSignAsync(digest, ed25519Seed); + return to0xHex(sig); + } + default: + throw new Error("unknownScheme"); + } +} + +/** + * @param {import('../constants.js').SignScheme} scheme + * @param {Uint8Array} payload + */ +export function validateSignPayload(scheme, payload) { + if (scheme === "ed25519") { + if (payload.length === 0) throw new Error("invalidPayload"); + return; + } + if (payload.length !== 32) { + throw new Error("digestMustBe32Bytes"); + } +} diff --git a/vendor/ows-signer/src/crypto/vendor/noble-ed25519.js b/vendor/ows-signer/src/crypto/vendor/noble-ed25519.js new file mode 100644 index 0000000..ab45032 --- /dev/null +++ b/vendor/ows-signer/src/crypto/vendor/noble-ed25519.js @@ -0,0 +1,385 @@ +/*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ +const P = 2n ** 255n - 19n; // ed25519 is twisted edwards curve +const N = 2n ** 252n + 27742317777372353535851937790883648493n; // curve's (group) order +const Gx = 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an; // base point x +const Gy = 0x6666666666666666666666666666666666666666666666666666666666666658n; // base point y +const CURVE = { + a: -1n, // where a=-1, d = -(121665/121666) == -(121665 * inv(121666)) mod P + d: 37095705934669439343138083508754565189542113879843219016388785533085940283555n, + p: P, n: N, h: 8, Gx, Gy // field prime, curve (group) order, cofactor +}; +const err = (m = '') => { throw new Error(m); }; // error helper, messes-up stack trace +const str = (s) => typeof s === 'string'; // is string +const isu8 = (a) => (a instanceof Uint8Array || + (a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')); +const au8 = (a, l) => // is Uint8Array (of specific length) + !isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ? + err('Uint8Array of valid length expected') : a; +const u8n = (data) => new Uint8Array(data); // creates Uint8Array +const toU8 = (a, len) => au8(str(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a +const mod = (a, b = P) => { let r = a % b; return r >= 0n ? r : b + r; }; // mod division +const isPoint = (p) => (p instanceof Point ? p : err('Point expected')); // is xyzt point +class Point { + constructor(ex, ey, ez, et) { + this.ex = ex; + this.ey = ey; + this.ez = ez; + this.et = et; + } + static fromAffine(p) { return new Point(p.x, p.y, 1n, mod(p.x * p.y)); } + static fromHex(hex, zip215 = false) { + const { d } = CURVE; + hex = toU8(hex, 32); + const normed = hex.slice(); // copy the array to not mess it up + const lastByte = hex[31]; + normed[31] = lastByte & ~0x80; // adjust first LE byte = last BE byte + const y = b2n_LE(normed); // decode as little-endian, convert to num + if (zip215 && !(0n <= y && y < 2n ** 256n)) + err('bad y coord 1'); // zip215=true [1..2^256-1] + if (!zip215 && !(0n <= y && y < P)) + err('bad y coord 2'); // zip215=false [1..P-1] + const y2 = mod(y * y); // y² + const u = mod(y2 - 1n); // u=y²-1 + const v = mod(d * y2 + 1n); // v=dy²+1 + let { isValid, value: x } = uvRatio(u, v); // (uv³)(uv⁷)^(p-5)/8; square root + if (!isValid) + err('bad y coordinate 3'); // not square root: bad point + const isXOdd = (x & 1n) === 1n; // adjust sign of x coordinate + const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit + if (!zip215 && x === 0n && isLastByteOdd) + err('bad y coord 3'); // x=0 and x_0 = 1 + if (isLastByteOdd !== isXOdd) + x = mod(-x); + return new Point(x, y, 1n, mod(x * y)); // Z=1, T=xy + } + get x() { return this.toAffine().x; } // .x, .y will call expensive toAffine. + get y() { return this.toAffine().y; } // Should be used with care. + equals(other) { + const { ex: X1, ey: Y1, ez: Z1 } = this; + const { ex: X2, ey: Y2, ez: Z2 } = isPoint(other); // isPoint() checks class equality + const X1Z2 = mod(X1 * Z2), X2Z1 = mod(X2 * Z1); + const Y1Z2 = mod(Y1 * Z2), Y2Z1 = mod(Y2 * Z1); + return X1Z2 === X2Z1 && Y1Z2 === Y2Z1; + } + is0() { return this.equals(I); } + negate() { + return new Point(mod(-this.ex), this.ey, this.ez, mod(-this.et)); + } + double() { + const { ex: X1, ey: Y1, ez: Z1 } = this; // Cost: 4M + 4S + 1*a + 6add + 1*2 + const { a } = CURVE; // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd + const A = mod(X1 * X1); + const B = mod(Y1 * Y1); + const C = mod(2n * mod(Z1 * Z1)); + const D = mod(a * A); + const x1y1 = X1 + Y1; + const E = mod(mod(x1y1 * x1y1) - A - B); + const G = D + B; + const F = G - C; + const H = D - B; + const X3 = mod(E * F); + const Y3 = mod(G * H); + const T3 = mod(E * H); + const Z3 = mod(F * G); + return new Point(X3, Y3, Z3, T3); + } + add(other) { + const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; // Cost: 8M + 1*k + 8add + 1*2. + const { ex: X2, ey: Y2, ez: Z2, et: T2 } = isPoint(other); // doesn't check if other on-curve + const { a, d } = CURVE; // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3 + const A = mod(X1 * X2); + const B = mod(Y1 * Y2); + const C = mod(T1 * d * T2); + const D = mod(Z1 * Z2); + const E = mod((X1 + Y1) * (X2 + Y2) - A - B); + const F = mod(D - C); + const G = mod(D + C); + const H = mod(B - a * A); + const X3 = mod(E * F); + const Y3 = mod(G * H); + const T3 = mod(E * H); + const Z3 = mod(F * G); + return new Point(X3, Y3, Z3, T3); + } + mul(n, safe = true) { + if (n === 0n) + return safe === true ? err('cannot multiply by 0') : I; + if (!(typeof n === 'bigint' && 0n < n && n < N)) + err('invalid scalar, must be < L'); + if (!safe && this.is0() || n === 1n) + return this; // safe=true bans 0. safe=false allows 0. + if (this.equals(G)) + return wNAF(n).p; // use wNAF precomputes for base points + let p = I, f = G; // init result point & fake point + for (let d = this; n > 0n; d = d.double(), n >>= 1n) { // double-and-add ladder + if (n & 1n) + p = p.add(d); // if bit is present, add to point + else if (safe) + f = f.add(d); // if not, add to fake for timing safety + } + return p; + } + multiply(scalar) { return this.mul(scalar); } // Aliases for compatibilty + clearCofactor() { return this.mul(BigInt(CURVE.h), false); } // multiply by cofactor + isSmallOrder() { return this.clearCofactor().is0(); } // check if P is small order + isTorsionFree() { + let p = this.mul(N / 2n, false).double(); // ensures the point is not "bad". + if (N % 2n) + p = p.add(this); // P^(N+1) // P*N == (P*(N/2))*2+P + return p.is0(); + } + toAffine() { + const { ex: x, ey: y, ez: z } = this; // (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy) + if (this.equals(I)) + return { x: 0n, y: 1n }; // fast-path for zero point + const iz = invert(z); // z^-1: invert z + if (mod(z * iz) !== 1n) + err('invalid inverse'); // (z * z^-1) must be 1, otherwise bad math + return { x: mod(x * iz), y: mod(y * iz) }; // x = x*z^-1; y = y*z^-1 + } + toRawBytes() { + const { x, y } = this.toAffine(); // convert to affine 2d point + const b = n2b_32LE(y); // encode number to 32 bytes + b[31] |= x & 1n ? 0x80 : 0; // store sign in first LE byte + return b; + } + toHex() { return b2h(this.toRawBytes()); } // encode to hex string +} +Point.BASE = new Point(Gx, Gy, 1n, mod(Gx * Gy)); // Generator / Base point +Point.ZERO = new Point(0n, 1n, 1n, 0n); // Identity / Zero point +const { BASE: G, ZERO: I } = Point; // Generator, identity points +const padh = (num, pad) => num.toString(16).padStart(pad, '0'); +const b2h = (b) => Array.from(b).map(e => padh(e, 2)).join(''); // bytes to hex +const h2b = (hex) => { + const l = hex.length; // error if not string, + if (!str(hex) || l % 2) + err('hex invalid 1'); // or has odd length like 3, 5. + const arr = u8n(l / 2); // create result array + for (let i = 0; i < arr.length; i++) { + const j = i * 2; + const h = hex.slice(j, j + 2); // hexByte. slice is faster than substr + const b = Number.parseInt(h, 16); // byte, created from string part + if (Number.isNaN(b) || b < 0) + err('hex invalid 2'); // byte must be valid 0 <= byte < 256 + arr[i] = b; + } + return arr; +}; +const n2b_32LE = (num) => h2b(padh(num, 32 * 2)).reverse(); // number to bytes LE +const b2n_LE = (b) => BigInt('0x' + b2h(u8n(au8(b)).reverse())); // bytes LE to num +const concatB = (...arrs) => { + const r = u8n(arrs.reduce((sum, a) => sum + au8(a).length, 0)); // create u8a of summed length + let pad = 0; // walk through each array, + arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type + return r; +}; +const invert = (num, md = P) => { + if (num === 0n || md <= 0n) + err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now + let a = mod(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n; + while (a !== 0n) { // uses euclidean gcd algorithm + const q = b / a, r = b % a; // not constant-time + const m = x - u * q, n = y - v * q; + b = a, a = r, x = u, y = v, u = m, v = n; + } + return b === 1n ? mod(x, md) : err('no inverse'); // b is gcd at this point +}; +const pow2 = (x, power) => { + let r = x; + while (power-- > 0n) { + r *= r; + r %= P; + } + return r; +}; +const pow_2_252_3 = (x) => { + const x2 = (x * x) % P; // x^2, bits 1 + const b2 = (x2 * x) % P; // x^3, bits 11 + const b4 = (pow2(b2, 2n) * b2) % P; // x^(2^4-1), bits 1111 + const b5 = (pow2(b4, 1n) * x) % P; // x^(2^5-1), bits 11111 + const b10 = (pow2(b5, 5n) * b5) % P; // x^(2^10) + const b20 = (pow2(b10, 10n) * b10) % P; // x^(2^20) + const b40 = (pow2(b20, 20n) * b20) % P; // x^(2^40) + const b80 = (pow2(b40, 40n) * b40) % P; // x^(2^80) + const b160 = (pow2(b80, 80n) * b80) % P; // x^(2^160) + const b240 = (pow2(b160, 80n) * b80) % P; // x^(2^240) + const b250 = (pow2(b240, 10n) * b10) % P; // x^(2^250) + const pow_p_5_8 = (pow2(b250, 2n) * x) % P; // < To pow to (p+3)/8, multiply it by x. + return { pow_p_5_8, b2 }; +}; +const RM1 = 19681161376707505956807079304988542015446066515923890162744021073123829784752n; // √-1 +const uvRatio = (u, v) => { + const v3 = mod(v * v * v); // v³ + const v7 = mod(v3 * v3 * v); // v⁷ + const pow = pow_2_252_3(u * v7).pow_p_5_8; // (uv⁷)^(p-5)/8 + let x = mod(u * v3 * pow); // (uv³)(uv⁷)^(p-5)/8 + const vx2 = mod(v * x * x); // vx² + const root1 = x; // First root candidate + const root2 = mod(x * RM1); // Second root candidate; RM1 is √-1 + const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root + const useRoot2 = vx2 === mod(-u); // If vx² = -u, set x <-- x * 2^((p-1)/4) + const noRoot = vx2 === mod(-u * RM1); // There is no valid root, vx² = -u√-1 + if (useRoot1) + x = root1; + if (useRoot2 || noRoot) + x = root2; // We return root2 anyway, for const-time + if ((mod(x) & 1n) === 1n) + x = mod(-x); // edIsNegative + return { isValid: useRoot1 || useRoot2, value: x }; +}; +const modL_LE = (hash) => mod(b2n_LE(hash), N); // modulo L; but little-endian +let _shaS; +const sha512a = (...m) => etc.sha512Async(...m); // Async SHA512 +const sha512s = (...m) => // Sync SHA512, not set by default + typeof _shaS === 'function' ? _shaS(...m) : err('etc.sha512Sync not set'); +const hash2extK = (hashed) => { + const head = hashed.slice(0, 32); // slice creates a copy, unlike subarray + head[0] &= 248; // Clamp bits: 0b1111_1000, + head[31] &= 127; // 0b0111_1111, + head[31] |= 64; // 0b0100_0000 + const prefix = hashed.slice(32, 64); // private key "prefix" + const scalar = modL_LE(head); // modular division over curve order + const point = G.mul(scalar); // public key point + const pointBytes = point.toRawBytes(); // point serialized to Uint8Array + return { head, prefix, scalar, point, pointBytes }; +}; +// RFC8032 5.1.5; getPublicKey async, sync. Hash priv key and extract point. +const getExtendedPublicKeyAsync = (priv) => sha512a(toU8(priv, 32)).then(hash2extK); +const getExtendedPublicKey = (priv) => hash2extK(sha512s(toU8(priv, 32))); +const getPublicKeyAsync = (priv) => getExtendedPublicKeyAsync(priv).then(p => p.pointBytes); +const getPublicKey = (priv) => getExtendedPublicKey(priv).pointBytes; +function hashFinish(asynchronous, res) { + if (asynchronous) + return sha512a(res.hashable).then(res.finish); + return res.finish(sha512s(res.hashable)); +} +const _sign = (e, rBytes, msg) => { + const { pointBytes: P, scalar: s } = e; + const r = modL_LE(rBytes); // r was created outside, reduce it modulo L + const R = G.mul(r).toRawBytes(); // R = [r]B + const hashable = concatB(R, P, msg); // dom2(F, C) || R || A || PH(M) + const finish = (hashed) => { + const S = mod(r + modL_LE(hashed) * s, N); // S = (r + k * s) mod L; 0 <= s < l + return au8(concatB(R, n2b_32LE(S)), 64); // 64-byte sig: 32b R.x + 32b LE(S) + }; + return { hashable, finish }; +}; +const signAsync = async (msg, privKey) => { + const m = toU8(msg); // RFC8032 5.1.6: sign msg with key async + const e = await getExtendedPublicKeyAsync(privKey); // pub,prfx + const rBytes = await sha512a(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M)) + return hashFinish(true, _sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature +}; +const sign = (msg, privKey) => { + const m = toU8(msg); // RFC8032 5.1.6: sign msg with key sync + const e = getExtendedPublicKey(privKey); // pub,prfx + const rBytes = sha512s(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M)) + return hashFinish(false, _sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature +}; +const dvo = { zip215: true }; +const _verify = (sig, msg, pub, opts = dvo) => { + msg = toU8(msg); // Message hex str/Bytes + sig = toU8(sig, 64); // Signature hex str/Bytes, must be 64 bytes + const { zip215 } = opts; // switch between zip215 and rfc8032 verif + let A, R, s, SB, hashable = new Uint8Array(); + try { + A = Point.fromHex(pub, zip215); // public key A decoded + R = Point.fromHex(sig.slice(0, 32), zip215); // 0 <= R < 2^256: ZIP215 R can be >= P + s = b2n_LE(sig.slice(32, 64)); // Decode second half as an integer S + SB = G.mul(s, false); // in the range 0 <= s < L + hashable = concatB(R.toRawBytes(), A.toRawBytes(), msg); // dom2(F, C) || R || A || PH(M) + } + catch (error) { } + const finish = (hashed) => { + if (SB == null) + return false; // false if try-catch catched an error + if (!zip215 && A.isSmallOrder()) + return false; // false for SBS: Strongly Binding Signature + const k = modL_LE(hashed); // decode in little-endian, modulo L + const RkA = R.add(A.mul(k, false)); // [8]R + [8][k]A' + return RkA.add(SB.negate()).clearCofactor().is0(); // [8][S]B = [8]R + [8][k]A' + }; + return { hashable, finish }; +}; +// RFC8032 5.1.7: verification async, sync +const verifyAsync = async (s, m, p, opts = dvo) => hashFinish(true, _verify(s, m, p, opts)); +const verify = (s, m, p, opts = dvo) => hashFinish(false, _verify(s, m, p, opts)); +const cr = () => // We support: 1) browsers 2) node.js 19+ + typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined; +const etc = { + bytesToHex: b2h, hexToBytes: h2b, concatBytes: concatB, + mod, invert, + randomBytes: (len = 32) => { + const crypto = cr(); // Can be shimmed in node.js <= 18 to prevent error: + // import { webcrypto } from 'node:crypto'; + // if (!globalThis.crypto) globalThis.crypto = webcrypto; + if (!crypto || !crypto.getRandomValues) + err('crypto.getRandomValues must be defined'); + return crypto.getRandomValues(u8n(len)); + }, + sha512Async: async (...messages) => { + const crypto = cr(); + if (!crypto || !crypto.subtle) + err('crypto.subtle or etc.sha512Async must be defined'); + const m = concatB(...messages); + return u8n(await crypto.subtle.digest('SHA-512', m.buffer)); + }, + sha512Sync: undefined, // Actual logic below +}; +Object.defineProperties(etc, { sha512Sync: { + configurable: false, get() { return _shaS; }, set(f) { if (!_shaS) + _shaS = f; }, + } }); +const utils = { + getExtendedPublicKeyAsync, getExtendedPublicKey, + randomPrivateKey: () => etc.randomBytes(32), + precompute(w = 8, p = G) { p.multiply(3n); w; return p; }, // no-op +}; +const W = 8; // Precomputes-related code. W = window size +const precompute = () => { + const points = []; // 10x sign(), 2x verify(). To achieve this, + const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate + let p = G, b = p; // a lot of points related to base point G. + for (let w = 0; w < windows; w++) { // Points are stored in array and used + b = p; // any time Gx multiplication is done. + points.push(b); // They consume 16-32 MiB of RAM. + for (let i = 1; i < 2 ** (W - 1); i++) { + b = b.add(p); + points.push(b); + } + p = b.double(); // Precomputes don't speed-up getSharedKey, + } // which multiplies user point by scalar, + return points; // when precomputes are using base point +}; +let Gpows = undefined; // precomputes for base point G +const wNAF = (n) => { + // Compared to other point mult methods, + const comp = Gpows || (Gpows = precompute()); // stores 2x less points using subtraction + const neg = (cnd, p) => { let n = p.negate(); return cnd ? n : p; }; // negate + let p = I, f = G; // f must be G, or could become I in the end + const windows = 1 + 256 / W; // W=8 17 windows + const wsize = 2 ** (W - 1); // W=8 128 window size + const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111 + const maxNum = 2 ** W; // W=8 256 + const shiftBy = BigInt(W); // W=8 8 + for (let w = 0; w < windows; w++) { + const off = w * wsize; + let wbits = Number(n & mask); // extract W bits. + n >>= shiftBy; // shift number by W bits. + if (wbits > wsize) { + wbits -= maxNum; + n += 1n; + } // split if bits > max: +224 => 256-32 + const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both + const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both + if (wbits === 0) { + f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point + } + else { // ^ can't add off2, off2 = I + p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point + } + } + return { p, f }; // return both real and fake points for JIT +}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul() +export { getPublicKey, getPublicKeyAsync, sign, verify, // Remove the export to easily use in REPL +signAsync, verifyAsync, CURVE, etc, utils, Point as ExtendedPoint }; // envs like browser console diff --git a/vendor/ows-signer/src/crypto/vendor/noble-secp256k1.js b/vendor/ows-signer/src/crypto/vendor/noble-secp256k1.js new file mode 100644 index 0000000..e1156d4 --- /dev/null +++ b/vendor/ows-signer/src/crypto/vendor/noble-secp256k1.js @@ -0,0 +1,602 @@ +/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ +/** + * 4KB JS implementation of secp256k1 signatures & ECDH. Compliant with RFC6979. + * @module + */ +const B256 = 2n ** 256n; +const P = B256 - 0x1000003d1n; // curve's field prime +const N = B256 - 0x14551231950b75fc4402da1732fc9bebfn; // curve (group) order +const Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n; // base point x +const Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n; // base point y +/** + * secp256k1 curve parameters. Equation is x³ + ax + b. + * Gx and Gy are generator coordinates. p is field order, n is group order. + */ +const CURVE = { + p: P, n: N, a: 0n, b: 7n, Gx, Gy +}; // exported variables incl. a, b +const fLen = 32; // field / group byte length +const curve = (x) => M(M(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; a=0 +const err = (m = '') => { throw new Error(m); }; // error helper, messes-up stack trace +const isB = (n) => typeof n === 'bigint'; // is big integer +const isS = (s) => typeof s === 'string'; // is string +const fe = (n) => isB(n) && 0n < n && n < P; // is field element (invertible) +const ge = (n) => isB(n) && 0n < n && n < N; // is group element +const isu8 = (a) => (a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array')); +const au8 = (a, l) => // assert is Uint8Array (of specific length) + !isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ? + err('Uint8Array expected') : a; +const u8n = (data) => new Uint8Array(data); // creates Uint8Array +const toU8 = (a, len) => au8(isS(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a +const M = (a, b = P) => { + const r = a % b; + return r >= 0n ? r : b + r; +}; +const aPoint = (p) => (p instanceof Point ? p : err('Point expected')); // is 3d point +/** Point in 3d xyz projective coordinates. 3d takes less inversions than 2d. */ +class Point { + constructor(px, py, pz) { + this.px = px; + this.py = py; + this.pz = pz; + Object.freeze(this); + } + /** Create 3d xyz point from 2d xy. (0, 0) => (0, 1, 0), not (0, 0, 1) */ + static fromAffine(p) { + return ((p.x === 0n) && (p.y === 0n)) ? I : new Point(p.x, p.y, 1n); + } + /** Convert Uint8Array or hex string to Point. */ + static fromHex(hex) { + hex = toU8(hex); // convert hex string to Uint8Array + let p = undefined; + const head = hex[0], tail = hex.subarray(1); // first byte is prefix, rest is data + const x = slc(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate + if (len === 33 && [0x02, 0x03].includes(head)) { // compressed points: 33b, start + if (!fe(x)) + err('Point hex invalid: x not FE'); // with byte 0x02 or 0x03. Check if 0 0n; d = d.double(), n >>= 1n) { // double-and-add ladder + if (n & 1n) + p = p.add(d); // if bit is present, add to point + else if (safe) + f = f.add(d); // if not, add to fake for timing safety + } + return p; + } + mulAddQUns(R, u1, u2) { + return this.mul(u1, false).add(R.mul(u2, false)).ok(); // Unsafe: do NOT use for stuff related + } // to private keys. Doesn't use Shamir trick + /** Convert point to 2d xy affine point. (x, y, z) ∋ (x=x/z, y=y/z) */ + toAffine() { + const { px: x, py: y, pz: z } = this; + if (this.equals(I)) + return { x: 0n, y: 0n }; // fast-path for zero point + if (z === 1n) + return { x, y }; // if z is 1, pass affine coordinates as-is + const iz = inv(z, P); // z^-1: invert z + if (M(z * iz) !== 1n) + err('inverse invalid'); // (z * z^-1) must be 1, otherwise bad math + return { x: M(x * iz), y: M(y * iz) }; // x = x*z^-1; y = y*z^-1 + } + /** Checks if the point is valid and on-curve. */ + assertValidity() { + const { x, y } = this.aff(); // convert to 2d xy affine point. + if (!fe(x) || !fe(y)) + err('Point invalid: x or y'); // x and y must be in range 0 < n < P + return M(y * y) === curve(x) ? // y² = x³ + ax + b, must be equal + this : err('Point invalid: not on curve'); + } + multiply(n) { return this.mul(n); } // Aliases to compress code + aff() { return this.toAffine(); } + ok() { return this.assertValidity(); } + toHex(isCompressed = true) { + const { x, y } = this.aff(); // convert to 2d xy affine point + const head = isCompressed ? ((y & 1n) === 0n ? '02' : '03') : '04'; // 0x02, 0x03, 0x04 prefix + return head + n2h(x) + (isCompressed ? '' : n2h(y)); // prefix||x and ||y + } + toRawBytes(isCompressed = true) { + return h2b(this.toHex(isCompressed)); // re-use toHex(), convert hex to bytes + } +} +/** Generator / base point */ +Point.BASE = new Point(Gx, Gy, 1n); +/** Identity / zero point */ +Point.ZERO = new Point(0n, 1n, 0n); +const { BASE: G, ZERO: I } = Point; // Generator, identity points +const padh = (n, pad) => n.toString(16).padStart(pad, '0'); +const b2h = (b) => Array.from(au8(b)).map(e => padh(e, 2)).join(''); // bytes to hex +const C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 }; // ASCII characters +const _ch = (ch) => { + if (ch >= C._0 && ch <= C._9) + return ch - C._0; // '2' => 50-48 + if (ch >= C.A && ch <= C.F) + return ch - (C.A - 10); // 'B' => 66-(65-10) + if (ch >= C.a && ch <= C.f) + return ch - (C.a - 10); // 'b' => 98-(97-10) + return; +}; +const h2b = (hex) => { + const e = 'hex invalid'; + if (!isS(hex)) + return err(e); + const hl = hex.length, al = hl / 2; + if (hl % 2) + return err(e); + const array = u8n(al); + for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { // treat each char as ASCII + const n1 = _ch(hex.charCodeAt(hi)); // parse first char, multiply it by 16 + const n2 = _ch(hex.charCodeAt(hi + 1)); // parse second char + if (n1 === undefined || n2 === undefined) + return err(e); + array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9 + } + return array; +}; +const b2n = (b) => BigInt('0x' + (b2h(b) || '0')); // bytes to number +const slc = (b, from, to) => b2n(b.slice(from, to)); // slice bytes num +const n2b = (num) => { + return isB(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected'); +}; +const n2h = (num) => b2h(n2b(num)); // number to 32b hex +const concatB = (...arrs) => { + const r = u8n(arrs.reduce((sum, a) => sum + au8(a).length, 0)); // create u8a of summed length + let pad = 0; // walk through each array, + arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type + return r; +}; +const inv = (num, md) => { + if (num === 0n || md <= 0n) + err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now + let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n; + while (a !== 0n) { // uses euclidean gcd algorithm + const q = b / a, r = b % a; // not constant-time + const m = x - u * q, n = y - v * q; + b = a, a = r, x = u, y = v, u = m, v = n; + } + return b === 1n ? M(x, md) : err('no inverse'); // b is gcd at this point +}; +const sqrt = (n) => { + let r = 1n; // So, a special, fast case. Paper: "Square Roots from 1;24,51,10 to Dan Shanks". + for (let num = n, e = (P + 1n) / 4n; e > 0n; e >>= 1n) { // powMod: modular exponentiation. + if (e & 1n) + r = (r * num) % P; // Uses exponentiation by squaring. + num = (num * num) % P; // Not constant-time. + } + return M(r * r) === n ? r : err('sqrt invalid'); // check if result is valid +}; +const toPriv = (p) => { + if (!isB(p)) + p = b2n(toU8(p, fLen)); // convert to bigint when bytes + return ge(p) ? p : err('private key invalid 3'); // check if bigint is in range +}; +const high = (n) => n > (N >> 1n); // if a number is bigger than CURVE.n/2 +/** Creates 33/65-byte public key from 32-byte private key. */ +const getPublicKey = (privKey, isCompressed = true) => { + return Point.fromPrivateKey(privKey).toRawBytes(isCompressed); +}; +/** ECDSA Signature class. Supports only compact 64-byte representation, not DER. */ +class Signature { + constructor(r, s, recovery) { + this.r = r; + this.s = s; + this.recovery = recovery; + this.assertValidity(); // recovery bit is optional when + } // constructed outside. + /** Create signature from 64b compact (r || s) representation. */ + static fromCompact(hex) { + hex = toU8(hex, 64); // compact repr is (32b r)||(32b s) + return new Signature(slc(hex, 0, fLen), slc(hex, fLen, 2 * fLen)); + } + assertValidity() { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n + /** Create new signature, with added recovery bit. */ + addRecoveryBit(rec) { + return new Signature(this.r, this.s, rec); + } + hasHighS() { return high(this.s); } + normalizeS() { + return high(this.s) ? new Signature(this.r, M(-this.s, N), this.recovery) : this; + } + /** ECDSA public key recovery. Requires msg hash and recovery id. */ + recoverPublicKey(msgh) { + const { r, s, recovery: rec } = this; // secg.org/sec1-v2.pdf 4.1.6 + if (![0, 1, 2, 3].includes(rec)) + err('recovery id invalid'); // check recovery id + const h = bits2int_modN(toU8(msgh, fLen)); // Truncate hash + const radj = rec === 2 || rec === 3 ? r + N : r; // If rec was 2 or 3, q.x is bigger than n + if (radj >= P) + err('q.x invalid'); // ensure q.x is still a field element + const head = (rec & 1) === 0 ? '02' : '03'; // head is 0x02 or 0x03 + const R = Point.fromHex(head + n2h(radj)); // concat head + hex repr of r + const ir = inv(radj, N); // r^-1 + const u1 = M(-h * ir, N); // -hr^-1 + const u2 = M(s * ir, N); // sr^-1 + return G.mulAddQUns(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1) + } + /** Uint8Array 64b compact (r || s) representation. */ + toCompactRawBytes() { return h2b(this.toCompactHex()); } + /** Hex string 64b compact (r || s) representation. */ + toCompactHex() { return n2h(this.r) + n2h(this.s); } +} +const bits2int = (bytes) => { + const delta = bytes.length * 8 - 256; // RFC suggests optional truncating via bits2octets + if (delta > 1024) + err('msg invalid'); // our CUSTOM check, "just-in-case" + const num = b2n(bytes); // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which + return delta > 0 ? num >> BigInt(delta) : num; // matches bits2int. bits2int can produce res>N. +}; +const bits2int_modN = (bytes) => { + return M(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors +}; +const i2o = (num) => n2b(num); // int to octets +const cr = () => // We support: 1) browsers 2) node.js 19+ 3) deno, other envs with crypto + typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined; +let _hmacSync; // Can be redefined by use in utils; built-ins don't provide it +const optS = { lowS: true }; // opts for sign() +const optV = { lowS: true }; // standard opts for verify() +const prepSig = (msgh, priv, opts = optS) => { + if (['der', 'recovered', 'canonical'].some(k => k in opts)) + err('option not supported'); // legacy opts + let { lowS } = opts; // generates low-s sigs by default + if (lowS == null) + lowS = true; // RFC6979 3.2: we skip step A + const h1i = bits2int_modN(toU8(msgh)); // msg bigint + const h1o = i2o(h1i); // msg octets + const d = toPriv(priv); // validate private key, convert to bigint + const seed = [i2o(d), h1o]; // Step D of RFC6979 3.2 + let ent = opts.extraEntropy; // RFC6979 3.6: additional k' (optional) + if (ent) // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') + seed.push(ent === true ? etc.randomBytes(fLen) : toU8(ent)); // true == fetch from CSPRNG + const m = h1i; // convert msg to bigint + const k2sig = (kBytes) => { + const k = bits2int(kBytes); // RFC6979 method. + if (!ge(k)) + return; // Check 0 < k < CURVE.n + const ik = inv(k, N); // k^-1 mod n, NOT mod P + const q = G.mul(k).aff(); // q = Gk + const r = M(q.x, N); // r = q.x mod n + if (r === 0n) + return; // r=0 invalid + const s = M(ik * M(m + M(d * r, N), N), N); // s = k^-1(m + rd) mod n + if (s === 0n) + return; // s=0 invalid + let normS = s; // normalized S + let rec = (q.x === r ? 0 : 2) | Number(q.y & 1n); // recovery bit + if (lowS && high(s)) { // if lowS was passed, ensure s is always + normS = M(-s, N); // in the bottom half of CURVE.n + rec ^= 1; + } + return new Signature(r, normS, rec); // use normS, not s + }; + return { seed: concatB(...seed), k2sig }; +}; +function hmacDrbg(asynchronous) { + let v = u8n(fLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs. + let k = u8n(fLen); // Steps B, C of RFC6979 3.2: set hashLen, in our case always same + let i = 0; // Iterations counter, will throw when over 1000 + const reset = () => { v.fill(1); k.fill(0); i = 0; }; + const _e = 'drbg: tried 1000 values'; + if (asynchronous) { // asynchronous=true + const h = (...b) => etc.hmacSha256Async(k, v, ...b); // hmac(k)(v, ...values) + const reseed = async (seed = u8n()) => { + k = await h(u8n([0x00]), seed); // k = hmac(K || V || 0x00 || seed) + v = await h(); // v = hmac(K || V) + if (seed.length === 0) + return; + k = await h(u8n([0x01]), seed); // k = hmac(K || V || 0x01 || seed) + v = await h(); // v = hmac(K || V) + }; + const gen = async () => { + if (i++ >= 1000) + err(_e); + v = await h(); // v = hmac(K || V) + return v; + }; + return async (seed, pred) => { + reset(); // the returned fn, don't, it's: 1. slower (JIT). 2. unsafe (async race conditions) + await reseed(seed); // Steps D-G + let res = undefined; // Step H: grind until k is in [1..n-1] + while (!(res = pred(await gen()))) + await reseed(); // test predicate until it returns ok + reset(); + return res; + }; + } + else { + const h = (...b) => { + const f = _hmacSync; + if (!f) + err('etc.hmacSha256Sync not set'); + return f(k, v, ...b); // hmac(k)(v, ...values) + }; + const reseed = (seed = u8n()) => { + k = h(u8n([0x00]), seed); // k = hmac(k || v || 0x00 || seed) + v = h(); // v = hmac(k || v) + if (seed.length === 0) + return; + k = h(u8n([0x01]), seed); // k = hmac(k || v || 0x01 || seed) + v = h(); // v = hmac(k || v) + }; + const gen = () => { + if (i++ >= 1000) + err(_e); + v = h(); // v = hmac(k || v) + return v; + }; + return (seed, pred) => { + reset(); + reseed(seed); // Steps D-G + let res = undefined; // Step H: grind until k is in [1..n-1] + while (!(res = pred(gen()))) + reseed(); // test predicate until it returns ok + reset(); + return res; + }; + } +} +; +/** ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k. */ +/** + * Sign a msg hash using secp256k1. Async. + * It is advised to use `extraEntropy: true` (from RFC6979 3.6) to prevent fault attacks. + * Worst case: if randomness source for extraEntropy is bad, it would be as secure as if + * the option has not been used. + * @param msgh - message HASH, not message itself e.g. sha256(message) + * @param priv - private key + * @param opts - `lowS: true` to prevent malleability (s >= CURVE.n/2), `extraEntropy: boolean | Hex` to improve sig security. + */ +const signAsync = async (msgh, priv, opts = optS) => { + const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg + return hmacDrbg(true)(seed, k2sig); // Re-run drbg until k2sig returns ok +}; +/** + * Sign a msg hash using secp256k1. + * It is advised to use `extraEntropy: true` (from RFC6979 3.6) to prevent fault attacks. + * Worst case: if randomness source for extraEntropy is bad, it would be as secure as if + * the option has not been used. + * @param msgh - message HASH, not message itself e.g. sha256(message) + * @param priv - private key + * @param opts - `lowS: true` to prevent malleability (s >= CURVE.n/2), `extraEntropy: boolean | Hex` to improve sig security. + * @example + * const sig = sign(sha256('hello'), privKey, { extraEntropy: true }).toCompactRawBytes(); + */ +const sign = (msgh, priv, opts = optS) => { + const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg + return hmacDrbg(false)(seed, k2sig); // Re-run drbg until k2sig returns ok +}; +/** + * Verify a signature using secp256k1. + * @param sig - signature, 64-byte or Signature instance + * @param msgh - message HASH, not message itself e.g. sha256(message) + * @param pub - public key + * @param opts - { lowS: true } is default, prohibits s >= CURVE.n/2 to prevent malleability + */ +const verify = (sig, msgh, pub, opts = optV) => { + let { lowS } = opts; // ECDSA signature verification + if (lowS == null) + lowS = true; // Default lowS=true + if ('strict' in opts) + err('option not supported'); // legacy param + let sig_, h, P; // secg.org/sec1-v2.pdf 4.1.4 + const rs = sig && typeof sig === 'object' && 'r' in sig; // Previous ver supported DER sigs. We + if (!rs && (toU8(sig).length !== 2 * fLen)) // throw error when DER is suspected now. + err('signature must be 64 bytes'); + try { + sig_ = rs ? new Signature(sig.r, sig.s).assertValidity() : Signature.fromCompact(sig); + h = bits2int_modN(toU8(msgh)); // Truncate hash + P = pub instanceof Point ? pub.ok() : Point.fromHex(pub); // Validate public key + } + catch (e) { + return false; + } // Check sig for validity in both cases + if (!sig_) + return false; + const { r, s } = sig_; + if (lowS && high(s)) + return false; // lowS bans sig.s >= CURVE.n/2 + let R; + try { + const is = inv(s, N); // s^-1 + const u1 = M(h * is, N); // u1 = hs^-1 mod n + const u2 = M(r * is, N); // u2 = rs^-1 mod n + R = G.mulAddQUns(P, u1, u2).aff(); // R = u1⋅G + u2⋅P + } + catch (error) { + return false; + } + if (!R) + return false; // stop if R is identity / zero point + const v = M(R.x, N); // R.x must be in N's field, not P's + return v === r; // mod(R.x, n) == r +}; +/** + * Elliptic Curve Diffie-Hellman (ECDH) on secp256k1. + * Result is **NOT hashed**. Use hash on it if you need. + * @param privA private key A + * @param pubB public key B + * @param isCompressed 33-byte or 65-byte output + * @returns public key C + */ +const getSharedSecret = (privA, pubB, isCompressed = true) => { + return Point.fromHex(pubB).mul(toPriv(privA)).toRawBytes(isCompressed); // ECDH +}; +const hashToPrivateKey = (hash) => { + hash = toU8(hash); // produces private keys with modulo bias + if (hash.length < fLen + 8 || hash.length > 1024) + err('expected 40-1024b'); // being neglible. + const num = M(b2n(hash), N - 1n); // takes n+8 bytes + return n2b(num + 1n); // returns (hash mod n-1)+1 +}; +/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */ +const etc = { + hexToBytes: h2b, + bytesToHex: b2h, + concatBytes: concatB, + bytesToNumberBE: b2n, + numberToBytesBE: n2b, + mod: M, + invert: inv, // math utilities + hmacSha256Async: async (key, ...msgs) => { + const c = cr(); // async HMAC-SHA256, no sync built-in! + const s = c && c.subtle; // For React Native support, see README. + if (!s) + return err('etc.hmacSha256Async or crypto.subtle must be defined'); // Uses webcrypto built-in cryptography. + const k = await s.importKey('raw', key, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']); + return u8n(await s.sign('HMAC', k, concatB(...msgs))); + }, + hmacSha256Sync: _hmacSync, // For TypeScript. Actual logic is below + hashToPrivateKey: hashToPrivateKey, + randomBytes: (len = 32) => { + const crypto = cr(); // Must be shimmed in node.js <= 18 to prevent error. See README. + if (!crypto || !crypto.getRandomValues) + err('crypto.getRandomValues must be defined'); + return crypto.getRandomValues(u8n(len)); + }, +}; +/** Curve-specific utilities for private keys. */ +const utils = { + normPrivateKeyToScalar: toPriv, + isValidPrivateKey: (key) => { try { + return !!toPriv(key); + } + catch (e) { + return false; + } }, + randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(fLen + 16)), // FIPS 186 B.4.1. + precompute: (w = 8, p = G) => { p.multiply(3n); w; return p; }, // no-op +}; +Object.defineProperties(etc, { hmacSha256Sync: { + configurable: false, get() { return _hmacSync; }, set(f) { if (!_hmacSync) + _hmacSync = f; }, + } }); +const W = 8; // Precomputes-related code. W = window size +const precompute = () => { + const points = []; // 10x sign(), 2x verify(). To achieve this, + const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate + let p = G, b = p; // a lot of points related to base point G. + for (let w = 0; w < windows; w++) { // Points are stored in array and used + b = p; // any time Gx multiplication is done. + points.push(b); // They consume 16-32 MiB of RAM. + for (let i = 1; i < 2 ** (W - 1); i++) { + b = b.add(p); + points.push(b); + } + p = b.double(); // Precomputes don't speed-up getSharedKey, + } // which multiplies user point by scalar, + return points; // when precomputes are using base point +}; +let Gpows = undefined; // precomputes for base point G +const wNAF = (n) => { + // Compared to other point mult methods, + const comp = Gpows || (Gpows = precompute()); // stores 2x less points using subtraction + const neg = (cnd, p) => { let n = p.negate(); return cnd ? n : p; }; // negate + let p = I, f = G; // f must be G, or could become I in the end + const windows = 1 + 256 / W; // W=8 17 windows + const wsize = 2 ** (W - 1); // W=8 128 window size + const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111 + const maxNum = 2 ** W; // W=8 256 + const shiftBy = BigInt(W); // W=8 8 + for (let w = 0; w < windows; w++) { + const off = w * wsize; + let wbits = Number(n & mask); // extract W bits. + n >>= shiftBy; // shift number by W bits. + if (wbits > wsize) { + wbits -= maxNum; + n += 1n; + } // split if bits > max: +224 => 256-32 + const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both + const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both + if (wbits === 0) { + f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point + } + else { // ^ can't add off2, off2 = I + p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point + } + } + return { p, f }; // return both real and fake points for JIT +}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul() +export { getPublicKey, sign, signAsync, verify, CURVE, // Remove the export to easily use in REPL +getSharedSecret, etc, utils, Point as ProjectivePoint, Signature }; // envs like browser console diff --git a/vendor/ows-signer/src/debug.js b/vendor/ows-signer/src/debug.js new file mode 100644 index 0000000..25daf61 --- /dev/null +++ b/vendor/ows-signer/src/debug.js @@ -0,0 +1,42 @@ +const DEBUG_KEY = "ows-signer:debug"; + +/** @returns {boolean} */ +export function isDebug() { + if (typeof globalThis === "undefined") return false; + return ( + globalThis.OWS_SIGNER_DEBUG === true || + (typeof localStorage !== "undefined" && + localStorage.getItem(DEBUG_KEY) === "1") + ); +} + +/** @param {...unknown} args */ +export function debugLog(...args) { + if (isDebug()) { + console.info("[ows-signer]", ...args); + } +} + +/** + * @param {PublicKeyCredential} credential + * @returns {Record} + */ +export function describePrfExtensionResults(credential) { + const prf = credential.getClientExtensionResults()?.prf; + const raw = prf?.results?.first; + return { + prfEnabled: prf?.enabled, + hasResults: Boolean(prf?.results), + resultKeys: prf?.results ? Object.keys(prf.results) : [], + prfKeys: prf ? Object.keys(prf) : [], + rawFirstType: typeof raw, + rawFirstIsArrayBuffer: raw instanceof ArrayBuffer, + rawFirstIsView: ArrayBuffer.isView(raw), + rawFirstByteLength: + raw instanceof ArrayBuffer + ? raw.byteLength + : ArrayBuffer.isView(raw) + ? raw.byteLength + : null, + }; +} diff --git a/vendor/ows-signer/src/handlers.js b/vendor/ows-signer/src/handlers.js new file mode 100644 index 0000000..93bc4b0 --- /dev/null +++ b/vendor/ows-signer/src/handlers.js @@ -0,0 +1,691 @@ +import { + API_VERSION, + METHODS, + PRF_LABEL_ED25519, + SIGN_SCHEMES, + SIGNER_VERSION, +} from "./constants.js"; +import { + decryptAes256Batch, + encryptAes256Batch, +} from "./crypto/aes256.js"; +import { + deriveEd25519SeedFromPrf, + deriveKeysFromCredential, + hkdfExpand, + normalizePrfOutputToArrayBuffer, +} from "./crypto/prf.js"; +import { getPublicKeyAsync as edGetPublicKeyAsync } from "./crypto/vendor/noble-ed25519.js"; +import { getPublicKey as secpGetPublicKey } from "./crypto/vendor/noble-secp256k1.js"; +import { debugLog, describePrfExtensionResults } from "./debug.js"; +import { decryptPrivateKey, encryptPrivateKey } from "./crypto/recovery.js"; +import { signWithScheme, validateSignPayload } from "./crypto/sign.js"; +import { parse0xHex, to0xHex } from "./hex.js"; +import { emitEvent } from "./rpc.js"; +import { + clearRecoveryPrivateKey, + getRecoveryPrivateKey, + hasRecoverySession, + setRecoveryPrivateKey, + withCeremony, + zeroize, +} from "./state.js"; +import { clearUi, promptPassphrase, showPrivateKey } from "./ui.js"; +import { + createPasskeyCredential, + getAssertionSignatureBase64Url, + getCredentialId, + getPasskeyAssertion, + getPasskeyPublicKeyBase64Url, +} from "./webauthn.js"; + +/** + * @param {PublicKeyCredential} credential + * @returns {Promise} + */ +async function ed25519SeedFromCredential(credential) { + const prfRaw = /** @type {{ prf?: { results?: { first?: unknown } } }} */ ( + credential.getClientExtensionResults() + ).prf?.results?.first; + const prfBuf = normalizePrfOutputToArrayBuffer(prfRaw); + if (!prfBuf) throw new Error("noPrf"); + return deriveEd25519SeedFromPrf(prfBuf); +} + +/** + * @param {Uint8Array} secp256k1Scalar + * @returns {Promise} + */ +async function ed25519SeedFromSecp256k1Scalar(secp256k1Scalar) { + return hkdfExpand( + secp256k1Scalar.buffer.slice( + secp256k1Scalar.byteOffset, + secp256k1Scalar.byteOffset + secp256k1Scalar.byteLength, + ), + PRF_LABEL_ED25519, + 32, + ); +} + +/** + * @param {string} targetOrigin + * @param {string | undefined} correlationId + * @param {Uint8Array} secp256k1PublicKey + * @param {Uint8Array} ed25519PublicKey + */ +function emitKeyDerived( + targetOrigin, + correlationId, + secp256k1PublicKey, + ed25519PublicKey, +) { + emitEvent(window.parent, targetOrigin, "KeyDerived", correlationId, { + secp256k1PublicKey: to0xHex(secp256k1PublicKey), + ed25519PublicKey: to0xHex(ed25519PublicKey), + }); +} + +/** + * @param {string} method + * @param {Record} params + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +export async function handleRequest( + method, + params, + correlationId, + targetOrigin, +) { + if (!METHODS.includes(method)) { + emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, { + reason: "unknownMethod", + }); + return; + } + + try { + switch (method) { + case "getVersion": + emitEvent(window.parent, targetOrigin, "Version", correlationId, { + apiVersion: API_VERSION, + signerVersion: SIGNER_VERSION, + recoverySessionActive: hasRecoverySession(), + }); + return; + + case "createCredential": + await handleCreateCredential(params, correlationId, targetOrigin); + return; + + case "signDigest": + await handleSignDigest(params, correlationId, targetOrigin); + return; + + case "revealPrivateKey": + await handleRevealPrivateKey(params, correlationId, targetOrigin); + return; + + case "createRecoveryData": + await handleCreateRecoveryData(params, correlationId, targetOrigin); + return; + + case "recoverKey": + await handleRecoverKey(params, correlationId, targetOrigin); + return; + + case "getPublicKey": + await handleGetPublicKey(params, correlationId, targetOrigin); + return; + + case "clearRecoverySession": + clearRecoveryPrivateKey(); + clearUi(); + emitEvent( + window.parent, + targetOrigin, + "RecoverySessionCleared", + correlationId, + {}, + ); + return; + + case "encryptAES256": + await handleEncryptAES256(params, correlationId, targetOrigin); + return; + + case "decryptAES256": + await handleDecryptAES256(params, correlationId, targetOrigin); + return; + } + } catch (error) { + handleError(error, correlationId, targetOrigin); + } +} + +/** + * PRF bytes are returned on assertion (`prf.eval`), not registration (`prf.enable`). + * Some platforms may return results on create; otherwise run a follow-up get. + * + * @param {PublicKeyCredential} credential + * @param {string} [credentialId] + * @returns {Promise} + */ +async function credentialForKeyDerivation(credential, credentialId) { + debugLog("ceremony extension results", describePrfExtensionResults(credential)); + + const rawPrf = credential.getClientExtensionResults()?.prf?.results?.first; + if (normalizePrfOutputToArrayBuffer(rawPrf)) { + debugLog("using PRF output from current ceremony"); + return credential; + } + + const id = credentialId ?? getCredentialId(credential); + debugLog("no PRF results on registration; running assertion with prf.eval", { + credentialId: id, + }); + const assertion = await getPasskeyAssertion(undefined, id); + debugLog( + "assertion extension results", + describePrfExtensionResults(assertion), + ); + return assertion; +} + +/** + * @param {Record} params + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +async function handleCreateCredential(params, correlationId, targetOrigin) { + const name = params.name; + if (typeof name !== "string" || !name) { + emitInvalid(correlationId, targetOrigin, "invalidName"); + return; + } + const options = + params.options && typeof params.options === "object" + ? /** @type {{ rpName?: string, userDisplayName?: string, userId?: string }} */ ( + params.options + ) + : {}; + + await withCeremony(async () => { + const credential = await createPasskeyCredential(name, options); + const credentialId = getCredentialId(credential); + const prfCredential = await credentialForKeyDerivation( + credential, + credentialId, + ); + const keys = await deriveKeysFromCredential(prfCredential); + emitKeyDerived( + targetOrigin, + correlationId, + keys.secp256k1PublicKey, + keys.ed25519PublicKey, + ); + + emitEvent(window.parent, targetOrigin, "CredentialCreated", correlationId, { + credentialId, + passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential), + secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey), + }); + zeroize(keys.secp256k1PrivateKey); + }); +} + +/** + * @param {Record} params + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +async function handleSignDigest(params, correlationId, targetOrigin) { + const digestData = params.digestData; + const scheme = params.scheme; + const credentialId = + typeof params.credentialId === "string" ? params.credentialId : undefined; + + if (typeof digestData !== "string" || typeof scheme !== "string") { + emitInvalid(correlationId, targetOrigin, "invalidParams"); + return; + } + if ( + !SIGN_SCHEMES.includes( + /** @type {import('./constants.js').SignScheme} */ (scheme), + ) + ) { + emitInvalid(correlationId, targetOrigin, "unknownScheme"); + return; + } + + const digest = parse0xHex(digestData); + validateSignPayload( + /** @type {import('./constants.js').SignScheme} */ (scheme), + digest, + ); + + if (hasRecoverySession()) { + const cached = getRecoveryPrivateKey(); + if (!cached) throw new Error("recoverySessionEmpty"); + const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(cached); + const signature = await signWithScheme( + /** @type {import('./constants.js').SignScheme} */ (scheme), + digest, + cached, + ed25519Seed, + ); + zeroize(ed25519Seed); + emitEvent(window.parent, targetOrigin, "DigestSigned", correlationId, { + digest: digestData, + scheme, + signature, + credentialId: credentialId ?? null, + }); + return; + } + + await withCeremony(async () => { + const credential = await getPasskeyAssertion(undefined, credentialId); + const keys = await deriveKeysFromCredential(credential); + const ed25519Seed = await ed25519SeedFromCredential(credential); + emitKeyDerived( + targetOrigin, + correlationId, + keys.secp256k1PublicKey, + keys.ed25519PublicKey, + ); + + const signature = await signWithScheme( + /** @type {import('./constants.js').SignScheme} */ (scheme), + digest, + keys.secp256k1PrivateKey, + ed25519Seed, + ); + zeroize(keys.secp256k1PrivateKey); + zeroize(ed25519Seed); + + emitEvent(window.parent, targetOrigin, "DigestSigned", correlationId, { + digest: digestData, + scheme, + signature, + credentialId: credentialId ?? getCredentialId(credential), + }); + }); +} + +/** + * @param {Record} params + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +async function handleRevealPrivateKey(params, correlationId, targetOrigin) { + const credentialId = + typeof params.credentialId === "string" ? params.credentialId : undefined; + + await withCeremony(async () => { + const credential = await getPasskeyAssertion(undefined, credentialId); + const keys = await deriveKeysFromCredential(credential); + emitKeyDerived( + targetOrigin, + correlationId, + keys.secp256k1PublicKey, + keys.ed25519PublicKey, + ); + showPrivateKey(keys.secp256k1PrivateKey); + zeroize(keys.secp256k1PrivateKey); + }); +} + +/** + * @param {Record} params + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +async function handleCreateRecoveryData(params, correlationId, targetOrigin) { + const passwordText = params.passwordText; + const buttonText = params.buttonText; + const minPasswordLength = params.minPasswordLength; + const credentialId = + typeof params.credentialId === "string" ? params.credentialId : undefined; + + if ( + typeof passwordText !== "string" || + typeof buttonText !== "string" || + typeof minPasswordLength !== "number" + ) { + emitInvalid(correlationId, targetOrigin, "invalidParams"); + return; + } + + const passphrase = await promptPassphrase( + passwordText, + buttonText, + minPasswordLength, + ); + + // Recovery session already holds the secp256k1 scalar — no WebAuthn needed. + if (hasRecoverySession()) { + const cached = getRecoveryPrivateKey(); + if (!cached) throw new Error("recoverySessionEmpty"); + const secp256k1PublicKey = secpGetPublicKey(cached, false); + const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(cached); + const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed); + emitKeyDerived( + targetOrigin, + correlationId, + secp256k1PublicKey, + ed25519PublicKey, + ); + const encryptedPrivateKey = await encryptPrivateKey(cached, passphrase); + zeroize(ed25519Seed); + clearUi(); + emitEvent( + window.parent, + targetOrigin, + "RecoveryDataCreated", + correlationId, + { encryptedPrivateKey }, + ); + return; + } + + await withCeremony(async () => { + const credential = await getPasskeyAssertion(undefined, credentialId); + const keys = await deriveKeysFromCredential(credential); + emitKeyDerived( + targetOrigin, + correlationId, + keys.secp256k1PublicKey, + keys.ed25519PublicKey, + ); + const encryptedPrivateKey = await encryptPrivateKey( + keys.secp256k1PrivateKey, + passphrase, + ); + zeroize(keys.secp256k1PrivateKey); + emitEvent( + window.parent, + targetOrigin, + "RecoveryDataCreated", + correlationId, + { encryptedPrivateKey }, + ); + }); +} + +/** + * @param {Record} params + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +async function handleRecoverKey(params, correlationId, targetOrigin) { + const envelope = params.aes256EncryptedPrivateKey; + const passwordText = params.passwordText; + const buttonText = params.buttonText; + const credentialId = + typeof params.credentialId === "string" ? params.credentialId : undefined; + + if ( + typeof envelope !== "string" || + typeof passwordText !== "string" || + typeof buttonText !== "string" + ) { + emitInvalid(correlationId, targetOrigin, "invalidParams"); + return; + } + + const passphrase = await promptPassphrase(passwordText, buttonText, 1); + const privateKey = await decryptPrivateKey(envelope, passphrase); + setRecoveryPrivateKey(privateKey); + + // Emit public keys so OWSSigner can cache addresses (uncompressed secp256k1 — + // viem publicKeyToAddress requires 0x04 ‖ X ‖ Y). + const secp256k1PublicKey = secpGetPublicKey(privateKey, false); + const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(privateKey); + const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed); + emitKeyDerived( + targetOrigin, + correlationId, + secp256k1PublicKey, + ed25519PublicKey, + ); + zeroize(ed25519Seed); + + showPrivateKey(privateKey); + + if (credentialId) { + await withCeremony(async () => { + await getPasskeyAssertion(undefined, credentialId); + clearRecoveryPrivateKey(); + }); + emitEvent( + window.parent, + targetOrigin, + "RecoverySessionCleared", + correlationId, + { rebound: true }, + ); + return; + } + + emitEvent( + window.parent, + targetOrigin, + "RecoverySessionStarted", + correlationId, + { recoverySessionActive: true }, + ); +} + +/** + * @param {unknown} value + * @returns {value is string[]} + */ +function isStringArray(value) { + return ( + Array.isArray(value) && value.every((item) => typeof item === "string") + ); +} + +/** + * Batch AES-256-GCM seal using the secp256k1 scalar (same as `signDigest`). + * + * @param {Record} params + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +async function handleEncryptAES256(params, correlationId, targetOrigin) { + const plaintexts = params.plaintexts; + const credentialId = + typeof params.credentialId === "string" ? params.credentialId : undefined; + + if (!isStringArray(plaintexts)) { + emitInvalid(correlationId, targetOrigin, "invalidParams"); + return; + } + + if (plaintexts.length === 0) { + emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, { + ciphertexts: [], + }); + return; + } + + if (hasRecoverySession()) { + const cached = getRecoveryPrivateKey(); + if (!cached) throw new Error("recoverySessionEmpty"); + const ciphertexts = await encryptAes256Batch(plaintexts, cached); + emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, { + ciphertexts, + }); + return; + } + + await withCeremony(async () => { + const credential = await getPasskeyAssertion(undefined, credentialId); + const keys = await deriveKeysFromCredential(credential); + emitKeyDerived( + targetOrigin, + correlationId, + keys.secp256k1PublicKey, + keys.ed25519PublicKey, + ); + try { + const ciphertexts = await encryptAes256Batch( + plaintexts, + keys.secp256k1PrivateKey, + ); + emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, { + ciphertexts, + }); + } finally { + zeroize(keys.secp256k1PrivateKey); + } + }); +} + +/** + * Batch AES-256-GCM unseal using the secp256k1 scalar (same as `signDigest`). + * + * @param {Record} params + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +async function handleDecryptAES256(params, correlationId, targetOrigin) { + const ciphertexts = params.ciphertexts; + const credentialId = + typeof params.credentialId === "string" ? params.credentialId : undefined; + + if (!isStringArray(ciphertexts)) { + emitInvalid(correlationId, targetOrigin, "invalidParams"); + return; + } + + if (ciphertexts.length === 0) { + emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, { + plaintexts: [], + }); + return; + } + + if (hasRecoverySession()) { + const cached = getRecoveryPrivateKey(); + if (!cached) throw new Error("recoverySessionEmpty"); + const plaintexts = await decryptAes256Batch(ciphertexts, cached); + emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, { + plaintexts, + }); + return; + } + + await withCeremony(async () => { + const credential = await getPasskeyAssertion(undefined, credentialId); + const keys = await deriveKeysFromCredential(credential); + emitKeyDerived( + targetOrigin, + correlationId, + keys.secp256k1PublicKey, + keys.ed25519PublicKey, + ); + try { + const plaintexts = await decryptAes256Batch( + ciphertexts, + keys.secp256k1PrivateKey, + ); + emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, { + plaintexts, + }); + } finally { + zeroize(keys.secp256k1PrivateKey); + } + }); +} + +/** + * @param {Record} params + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +async function handleGetPublicKey(params, correlationId, targetOrigin) { + const credentialId = + typeof params.credentialId === "string" ? params.credentialId : undefined; + const challenge = + typeof params.challenge === "string" + ? parse0xHex(params.challenge) + : undefined; + + await withCeremony(async () => { + const credential = await getPasskeyAssertion(challenge, credentialId); + const keys = await deriveKeysFromCredential(credential); + emitKeyDerived( + targetOrigin, + correlationId, + keys.secp256k1PublicKey, + keys.ed25519PublicKey, + ); + + emitEvent(window.parent, targetOrigin, "PublicKey", correlationId, { + credentialId: getCredentialId(credential), + passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential), + secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey), + ed25519PublicKey: to0xHex(keys.ed25519PublicKey), + }); + + if (challenge) { + const signature = getAssertionSignatureBase64Url(credential); + if (signature) { + emitEvent( + window.parent, + targetOrigin, + "ChallengeSigned", + correlationId, + { + challenge: to0xHex(challenge), + signature, + }, + ); + } + } + + zeroize(keys.secp256k1PrivateKey); + }); +} + +/** + * @param {unknown} error + * @param {string | undefined} correlationId + * @param {string} targetOrigin + */ +function handleError(error, correlationId, targetOrigin) { + const name = error instanceof Error ? error.name : ""; + const message = error instanceof Error ? error.message : String(error); + + if (name === "NotAllowedError" || message.includes("NotAllowed")) { + emitEvent(window.parent, targetOrigin, "NotAllowed", correlationId, { + reason: message || "notAllowed", + }); + return; + } + + if (message === "ceremonyInProgress") { + emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, { + reason: "ceremonyInProgress", + }); + return; + } + + emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, { + reason: message || "error", + }); +} + +/** + * @param {string | undefined} correlationId + * @param {string} targetOrigin + * @param {string} reason + */ +function emitInvalid(correlationId, targetOrigin, reason) { + emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, { + reason, + }); +} diff --git a/vendor/ows-signer/src/hex.js b/vendor/ows-signer/src/hex.js new file mode 100644 index 0000000..96d9e08 --- /dev/null +++ b/vendor/ows-signer/src/hex.js @@ -0,0 +1,68 @@ +/** + * @param {string} hex - with or without `0x` prefix + * @returns {Uint8Array} + */ +export function hexToBytes(hex) { + if (hex.startsWith("0x") || hex.startsWith("0X")) { + hex = hex.slice(2); + } + const out = new Uint8Array(hex.length / 2); + for (let i = 0; i < out.length; i++) { + out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16); + } + return out; +} + +/** + * @param {unknown} value + * @param {number} [expectedBytes] + * @returns {Uint8Array} + */ +export function parse0xHex(value, expectedBytes) { + if (typeof value !== "string" || !value.startsWith("0x")) { + throw new Error("Expected 0x-prefixed hex string"); + } + const hex = value.slice(2); + if (hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) { + throw new Error("Invalid hex string"); + } + const bytes = hexToBytes(hex); + if (expectedBytes !== undefined && bytes.length !== expectedBytes) { + throw new Error(`Expected ${expectedBytes} bytes, got ${bytes.length}`); + } + return bytes; +} + +/** + * @param {Uint8Array} bytes + * @returns {`0x${string}`} + */ +export function to0xHex(bytes) { + return `0x${Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join("")}`; +} + +/** + * @param {ArrayBuffer | Uint8Array} buffer + * @returns {string} + */ +export function bufferToBase64Url(buffer) { + const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); + let binary = ""; + for (const b of bytes) binary += String.fromCharCode(b); + return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); +} + +/** + * @param {string} value + * @returns {Uint8Array} + */ +export function base64UrlToBytes(value) { + const pad = "=".repeat((4 - (value.length % 4)) % 4); + const b64 = value.replace(/-/g, "+").replace(/_/g, "/") + pad; + const binary = atob(b64); + const out = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i); + return out; +} diff --git a/vendor/ows-signer/src/main.js b/vendor/ows-signer/src/main.js new file mode 100644 index 0000000..13ba833 --- /dev/null +++ b/vendor/ows-signer/src/main.js @@ -0,0 +1,40 @@ +import { handleRequest } from "./handlers.js"; +import { + isValidNesting, + setTrustedParentOrigin, +} from "./state.js"; +import { initUi } from "./ui.js"; +import { isValidParentMessage, parseRequest } from "./rpc.js"; + +function bootstrap() { + const root = document.getElementById("ows-signer-root"); + if (!root) { + throw new Error("OWS signer root element missing"); + } + initUi(root); + + if (!isValidNesting()) { + console.error("[ows-signer] Must be embedded in a wallet iframe (double iframe)."); + return; + } + + window.addEventListener("message", async (event) => { + if (!isValidParentMessage(event)) return; + if (!parseRequest(event.data)) return; + + setTrustedParentOrigin(event.origin); + const targetOrigin = event.origin; + const { method, correlationId, params = {} } = /** @type {{ method: string, correlationId?: string, params?: Record }} */ ( + event.data + ); + + await handleRequest( + method, + /** @type {Record} */ (params), + correlationId, + targetOrigin, + ); + }); +} + +bootstrap(); diff --git a/vendor/ows-signer/src/rpc.js b/vendor/ows-signer/src/rpc.js new file mode 100644 index 0000000..bfa641c --- /dev/null +++ b/vendor/ows-signer/src/rpc.js @@ -0,0 +1,48 @@ +/** + * @typedef {(event: string, correlationId: string | undefined, data: Record) => void} EmitFn + */ + +/** + * @param {Window} target + * @param {string} targetOrigin + * @param {string} event + * @param {string | undefined} correlationId + * @param {Record} data + */ +export function emitEvent(target, targetOrigin, event, correlationId, data) { + target.postMessage( + { + v: 1, + kind: "event", + event, + correlationId, + data, + }, + targetOrigin, + ); +} + +/** + * @param {MessageEvent} event + * @returns {boolean} + */ +export function isValidParentMessage(event) { + if (event.source !== window.parent) return false; + if (window.parent === window.top) return false; + if (event.source === window) return false; + return true; +} + +/** + * @param {unknown} data + * @returns {data is { v: number, kind: 'request', method: string, correlationId?: string, params?: Record }} + */ +export function parseRequest(data) { + if (!data || typeof data !== "object") return false; + const msg = /** @type {Record} */ (data); + return ( + msg.v === 1 && + msg.kind === "request" && + typeof msg.method === "string" + ); +} diff --git a/vendor/ows-signer/src/state.js b/vendor/ows-signer/src/state.js new file mode 100644 index 0000000..c04393c --- /dev/null +++ b/vendor/ows-signer/src/state.js @@ -0,0 +1,96 @@ +/** @type {Uint8Array | null} */ +let recoveryPrivateKey = null; + +/** @type {Promise | null} */ +let ceremonyPromise = null; + +/** @type {string | null} */ +let trustedParentOrigin = null; + +/** + * @returns {boolean} + */ +export function isValidNesting() { + try { + return window.parent !== window && window.parent !== window.top; + } catch { + return false; + } +} + +/** + * @returns {string} + */ +export function getRpId() { + return window.location.hostname; +} + +/** + * @param {string} origin + */ +export function setTrustedParentOrigin(origin) { + if (trustedParentOrigin === null) { + trustedParentOrigin = origin; + } +} + +/** + * @returns {string | null} + */ +export function getTrustedParentOrigin() { + return trustedParentOrigin; +} + +/** + * @returns {boolean} + */ +export function hasRecoverySession() { + return recoveryPrivateKey !== null; +} + +/** + * @returns {Uint8Array | null} + */ +export function getRecoveryPrivateKey() { + return recoveryPrivateKey; +} + +/** + * @param {Uint8Array} key + */ +export function setRecoveryPrivateKey(key) { + clearRecoveryPrivateKey(); + recoveryPrivateKey = key; +} + +export function clearRecoveryPrivateKey() { + if (recoveryPrivateKey) { + recoveryPrivateKey.fill(0); + recoveryPrivateKey = null; + } +} + +/** + * @param {() => Promise} fn + * @returns {Promise} + * @template T + */ +export async function withCeremony(fn) { + if (ceremonyPromise) { + throw new Error("ceremonyInProgress"); + } + const promise = fn(); + ceremonyPromise = promise; + try { + return await promise; + } finally { + ceremonyPromise = null; + } +} + +/** + * @param {Uint8Array} bytes + */ +export function zeroize(bytes) { + if (bytes) bytes.fill(0); +} diff --git a/vendor/ows-signer/src/ui.js b/vendor/ows-signer/src/ui.js new file mode 100644 index 0000000..4940b72 --- /dev/null +++ b/vendor/ows-signer/src/ui.js @@ -0,0 +1,68 @@ +import { to0xHex } from "./hex.js"; + +/** @type {HTMLElement | null} */ +let root = null; + +/** + * @param {HTMLElement} container + */ +export function initUi(container) { + root = container; + root.innerHTML = ""; + root.className = "ows-signer-ui"; +} + +export function clearUi() { + if (root) root.innerHTML = ""; +} + +/** + * @param {Uint8Array} privateKey + */ +export function showPrivateKey(privateKey) { + if (!root) return; + clearUi(); + const pre = document.createElement("pre"); + pre.className = "ows-key"; + pre.textContent = to0xHex(privateKey); + const copyBtn = document.createElement("button"); + copyBtn.type = "button"; + copyBtn.className = "ows-copy"; + copyBtn.textContent = "Copy"; + copyBtn.addEventListener("click", async () => { + await navigator.clipboard.writeText(pre.textContent ?? ""); + }); + root.append(pre, copyBtn); +} + +/** + * @param {string} passwordText + * @param {string} buttonText + * @param {number} minPasswordLength + * @returns {Promise} + */ +export function promptPassphrase(passwordText, buttonText, minPasswordLength) { + if (!root) { + return Promise.reject(new Error("uiNotInitialized")); + } + clearUi(); + return new Promise((resolve, reject) => { + const input = document.createElement("input"); + input.type = "password"; + input.className = "ows-password"; + input.placeholder = passwordText; + const button = document.createElement("button"); + button.type = "button"; + button.className = "ows-confirm"; + button.textContent = buttonText; + button.addEventListener("click", () => { + if (input.value.length < minPasswordLength) { + reject(new Error("passwordTooShort")); + return; + } + resolve(input.value); + clearUi(); + }); + root.append(input, button); + }); +} diff --git a/vendor/ows-signer/src/webauthn.js b/vendor/ows-signer/src/webauthn.js new file mode 100644 index 0000000..f01abe8 --- /dev/null +++ b/vendor/ows-signer/src/webauthn.js @@ -0,0 +1,150 @@ +import { getRpId } from "./state.js"; +import { PRF_LABEL_SECP256K1 } from "./constants.js"; +import { bufferToBase64Url } from "./hex.js"; +import { debugLog, describePrfExtensionResults } from "./debug.js"; + +/** + * @param {Uint8Array} [userId] + * @returns {Uint8Array} + */ +function randomUserId(userId) { + if (userId) return userId; + return crypto.getRandomValues(new Uint8Array(16)); +} + +/** + * @param {string} name + * @param {{ rpName?: string, userDisplayName?: string, userId?: string }} [options] + * @returns {Promise} + */ +export async function createPasskeyCredential(name, options = {}) { + const rpId = getRpId(); + const rpName = + typeof options.rpName === "string" && options.rpName + ? options.rpName + : "OWS"; + const userId = options.userId + ? Uint8Array.from(atob(options.userId), (c) => c.charCodeAt(0)) + : randomUserId(); + const displayName = options.userDisplayName ?? name; + + debugLog("createPasskeyCredential userActivation.isActive", { + isActive: navigator.userActivation?.isActive ?? false, + }); + + const credential = await navigator.credentials.create({ + publicKey: { + rp: { name: rpName, id: rpId }, + user: { + id: userId, + name, + displayName, + }, + challenge: crypto.getRandomValues(new Uint8Array(32)), + pubKeyCredParams: [{ type: "public-key", alg: -7 }], + authenticatorSelection: { + residentKey: "required", + requireResidentKey: true, + userVerification: "required", + }, + extensions: { + prf: { + enable: { + first: PRF_LABEL_SECP256K1, + }, + }, + }, + }, + }); + + if (!credential || !(credential instanceof PublicKeyCredential)) { + throw new Error("credentialCreationFailed"); + } + debugLog("createPasskeyCredential", describePrfExtensionResults(credential)); + return credential; +} + +/** + * @param {Uint8Array} [challenge] + * @param {string} [credentialId] + * @returns {Promise} + */ +export async function getPasskeyAssertion(challenge, credentialId) { + const rpId = getRpId(); + const allowCredentials = credentialId + ? [ + { + type: "public-key", + id: base64UrlToBytes(credentialId), + }, + ] + : undefined; + + const credential = await navigator.credentials.get({ + publicKey: { + challenge: challenge ?? crypto.getRandomValues(new Uint8Array(32)), + rpId, + userVerification: "required", + allowCredentials, + extensions: { + prf: { + eval: { + first: PRF_LABEL_SECP256K1, + }, + }, + }, + }, + }); + + if (!credential || !(credential instanceof PublicKeyCredential)) { + throw new Error("credentialGetFailed"); + } + debugLog("getPasskeyAssertion", describePrfExtensionResults(credential)); + return credential; +} + +/** + * @param {string} value + * @returns {Uint8Array} + */ +function base64UrlToBytes(value) { + const pad = "=".repeat((4 - (value.length % 4)) % 4); + const b64 = value.replace(/-/g, "+").replace(/_/g, "/") + pad; + const binary = atob(b64); + const out = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i); + return out; +} + +/** + * @param {PublicKeyCredential} credential + * @returns {string} + */ +export function getCredentialId(credential) { + return bufferToBase64Url(credential.rawId); +} + +/** + * @param {PublicKeyCredential} credential + * @returns {string | null} + */ +export function getPasskeyPublicKeyBase64Url(credential) { + const response = credential.response; + if ("getPublicKey" in response && typeof response.getPublicKey === "function") { + const key = response.getPublicKey(); + return key ? bufferToBase64Url(key) : null; + } + return null; +} + +/** + * @param {PublicKeyCredential} credential + * @returns {string | null} + */ +export function getAssertionSignatureBase64Url(credential) { + const response = credential.response; + if ("signature" in response && response.signature) { + return bufferToBase64Url(response.signature); + } + return null; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..2cff69a --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,131 @@ +import path from "node:path"; +import fs from "node:fs"; +import type { IncomingMessage, ServerResponse } from "node:http"; +import { fileURLToPath } from "node:url"; +import { defineConfig, type Plugin } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const vendorSignerRoot = path.resolve(__dirname, "vendor/ows-signer"); +const vendorSignerSrc = path.join(vendorSignerRoot, "src"); +const signerPublicDir = path.resolve(__dirname, "signer-static"); + +const MIME: Record = { + ".html": "text/html; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".mjs": "text/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".json": "application/json", + ".map": "application/json", + ".svg": "image/svg+xml", + ".woff2": "font/woff2", +}; + +function contentType(filePath: string): string { + return MIME[path.extname(filePath)] ?? "application/octet-stream"; +} + +function sendFile(res: ServerResponse, filePath: string): void { + res.statusCode = 200; + res.setHeader("Content-Type", contentType(filePath)); + fs.createReadStream(filePath).pipe(res); +} + +/** + * Resolve `rel` under `rootDir`, rejecting path traversal (`..`, encoded separators). + * Returns `undefined` when the result would escape `rootDir`. + */ +function resolveContainedPath( + rootDir: string, + urlRelative: string, +): string | undefined { + let rel: string; + try { + rel = decodeURIComponent(urlRelative); + } catch { + return undefined; + } + if (rel.includes("\0")) { + return undefined; + } + const root = path.resolve(rootDir); + const resolved = path.resolve(root, rel); + const rootPrefix = root.endsWith(path.sep) ? root : `${root}${path.sep}`; + if (resolved !== root && !resolved.startsWith(rootPrefix)) { + return undefined; + } + return resolved; +} + +/** Serve `/signer/` outside Vite `base` so WebAuthn + nest stay same-origin. */ +function serveSignerPlugin(): Plugin { + return { + name: "ows-serve-signer", + configureServer(server) { + server.middlewares.use((req: IncomingMessage, res: ServerResponse, next) => { + const url = req.url?.split("?")[0] ?? ""; + if (!url.startsWith("/signer")) { + next(); + return; + } + + if (url === "/signer" || url === "/signer/") { + sendFile(res, path.join(signerPublicDir, "index.html")); + return; + } + + if (url.startsWith("/signer/src/")) { + const filePath = resolveContainedPath( + vendorSignerSrc, + url.slice("/signer/src/".length), + ); + if (!filePath || !fs.existsSync(filePath)) { + res.statusCode = 404; + res.end("Not found"); + return; + } + sendFile(res, filePath); + return; + } + + const filePath = resolveContainedPath( + signerPublicDir, + url.slice("/signer/".length), + ); + if (!filePath || !fs.existsSync(filePath)) { + next(); + return; + } + sendFile(res, filePath); + }); + }, + }; +} + +export default defineConfig({ + base: "/wallet/", + publicDir: false, + plugins: [react(), tailwindcss(), serveSignerPlugin()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + server: { + port: Number(process.env.PORT ?? 5174), + strictPort: true, + host: "0.0.0.0", + allowedHosts: true, + }, + preview: { + port: Number(process.env.PORT ?? 5174), + strictPort: true, + host: "0.0.0.0", + }, + build: { + outDir: "dist/wallet", + emptyOutDir: true, + sourcemap: true, + }, +}); From 727a94f92060c012f0d304c28809b3d5df5aa14a Mon Sep 17 00:00:00 2001 From: Charlie Sibbach Date: Tue, 14 Jul 2026 19:16:56 -0700 Subject: [PATCH 02/41] Refactor project structure to utilize @1shotapi packages for the Signing Layer, removing the vendor directory. Update Dockerfile, package.json, and README to reflect new dependencies and build process. Implement wallet functionality with onboarding, backup, and credential management components. Enhance UI with modals and panels for user interaction. --- Dockerfile | 1 - README.md | 14 +- package-lock.json | 478 +++++++++++- package.json | 9 +- scripts/copy-signer.mjs | 11 +- scripts/sync-signer.mjs | 37 - src/App.tsx | 36 +- src/components/MainPanel.tsx | 116 +++ src/components/Modal.tsx | 100 +++ src/components/ModalHost.tsx | 79 ++ src/components/OnboardingPanel.tsx | 39 + src/components/SignerHost.tsx | 7 + src/components/WalletChrome.tsx | 24 + src/components/modals/BackupModals.tsx | 351 +++++++++ src/components/modals/CredentialModals.tsx | 147 ++++ src/components/modals/SetupModals.tsx | 136 ++++ src/components/modals/SignModals.tsx | 123 ++++ src/demo/demo-keys.ts | 10 + src/demo/in-memory-trust-registry.ts | 72 ++ src/demo/local-storage-store.ts | 159 ++++ src/index.css | 141 +--- src/main.tsx | 7 +- src/ows/demoChains.ts | 24 + src/ows/registerAccountConnect.ts | 63 ++ src/ows/registerApprovalSigning.ts | 38 + src/ows/registerCredentialsProvider.ts | 64 ++ src/storage.ts | 69 ++ src/wallet/WalletProvider.tsx | 693 ++++++++++++++++++ src/wallet/modalTypes.ts | 83 +++ vendor/ows-signer/package.json | 37 - vendor/ows-signer/src/constants.js | 30 - vendor/ows-signer/src/crypto/aes256.js | 115 --- vendor/ows-signer/src/crypto/prf.js | 119 --- vendor/ows-signer/src/crypto/recovery.js | 81 -- vendor/ows-signer/src/crypto/sign.js | 59 -- .../src/crypto/vendor/noble-ed25519.js | 385 ---------- .../src/crypto/vendor/noble-secp256k1.js | 602 --------------- vendor/ows-signer/src/debug.js | 42 -- vendor/ows-signer/src/handlers.js | 691 ----------------- vendor/ows-signer/src/hex.js | 68 -- vendor/ows-signer/src/main.js | 40 - vendor/ows-signer/src/rpc.js | 48 -- vendor/ows-signer/src/state.js | 96 --- vendor/ows-signer/src/ui.js | 68 -- vendor/ows-signer/src/webauthn.js | 150 ---- vite.config.ts | 8 +- 46 files changed, 2951 insertions(+), 2819 deletions(-) delete mode 100644 scripts/sync-signer.mjs create mode 100644 src/components/MainPanel.tsx create mode 100644 src/components/Modal.tsx create mode 100644 src/components/ModalHost.tsx create mode 100644 src/components/OnboardingPanel.tsx create mode 100644 src/components/SignerHost.tsx create mode 100644 src/components/WalletChrome.tsx create mode 100644 src/components/modals/BackupModals.tsx create mode 100644 src/components/modals/CredentialModals.tsx create mode 100644 src/components/modals/SetupModals.tsx create mode 100644 src/components/modals/SignModals.tsx create mode 100644 src/demo/demo-keys.ts create mode 100644 src/demo/in-memory-trust-registry.ts create mode 100644 src/demo/local-storage-store.ts create mode 100644 src/ows/demoChains.ts create mode 100644 src/ows/registerAccountConnect.ts create mode 100644 src/ows/registerApprovalSigning.ts create mode 100644 src/ows/registerCredentialsProvider.ts create mode 100644 src/storage.ts create mode 100644 src/wallet/WalletProvider.tsx create mode 100644 src/wallet/modalTypes.ts delete mode 100644 vendor/ows-signer/package.json delete mode 100644 vendor/ows-signer/src/constants.js delete mode 100644 vendor/ows-signer/src/crypto/aes256.js delete mode 100644 vendor/ows-signer/src/crypto/prf.js delete mode 100644 vendor/ows-signer/src/crypto/recovery.js delete mode 100644 vendor/ows-signer/src/crypto/sign.js delete mode 100644 vendor/ows-signer/src/crypto/vendor/noble-ed25519.js delete mode 100644 vendor/ows-signer/src/crypto/vendor/noble-secp256k1.js delete mode 100644 vendor/ows-signer/src/debug.js delete mode 100644 vendor/ows-signer/src/handlers.js delete mode 100644 vendor/ows-signer/src/hex.js delete mode 100644 vendor/ows-signer/src/main.js delete mode 100644 vendor/ows-signer/src/rpc.js delete mode 100644 vendor/ows-signer/src/state.js delete mode 100644 vendor/ows-signer/src/ui.js delete mode 100644 vendor/ows-signer/src/webauthn.js diff --git a/Dockerfile b/Dockerfile index a4e08ae..861f1c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,6 @@ COPY index.html vite.config.ts tsconfig.json tsconfig.node.json components.json COPY src ./src COPY scripts ./scripts COPY signer-static ./signer-static -COPY vendor/ows-signer ./vendor/ows-signer RUN npm run build diff --git a/README.md b/README.md index 46e537e..e0f5a06 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ 1Shot API's free, permissionless embedded wallet. Includes passkey-based verification and OID4 credential management for shared KYC. -This repo is the **OWS Branding Layer** for the 1Shot Wallet — a frontend-only static app (React + Vite + Tailwind + shadcn/ui) that will be hosted at `wallet.1shotapi.com`. +This repo is the **OWS Branding Layer** for the 1Shot Wallet — a frontend-only static app (React + Vite + Tailwind) hosted at `wallet.1shotapi.com`. ``` Host Layer (integrator dapp) - └── This app /wallet/ Branding Layer - └── /signer/ OWS Signing Layer (vendored ows-signer, unbundled) + └── This app /wallet/ Branding Layer (@1shotapi/ows-wallet-utils) + └── /signer/ Signing Layer (@1shotapi/ows-signer) ``` ## Stack @@ -23,12 +23,9 @@ Production deliverable: a static **nginx** Docker image (no server-side runtime) ```bash npm install -npm run sync:signer # copies ../prf-wallet/packages/ows-signer → vendor/ows-signer cp .env.example .env # set NGROK_AUTHTOKEN (and optional NGROK_DOMAIN) ``` -`sync:signer` defaults to sibling `../prf-wallet/packages/ows-signer`. Override with `OWS_SIGNER_SRC`. - ## Develop ```bash @@ -64,11 +61,10 @@ docker run --rm -p 8080:80 oneshot-wallet | Script | Purpose | |--------|---------| | `dev` / `dev:local` | Dev server (± ngrok) | -| `build` | Typecheck + Vite build + copy signer | -| `sync:signer` | Refresh vendored Signing Layer | +| `build` | Typecheck + Vite build + copy signer from `node_modules` | | `clean` | Remove `dist/` | | `lint` | `tsc --noEmit` | ## Status -Scaffold only — OWS helpers (`OWSWallet`, `SignHelper`, credentials ↔ relayer) not wired yet. +Functional Branding Layer ported from OWS `examples/general-wallet` (passkey unlock, EIP-1193, signing consent, recovery, credentials). UI will be refactored to shadcn next. diff --git a/package-lock.json b/package-lock.json index 175de56..8ee469b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,11 @@ "name": "@1shotapi/embedded-wallet", "version": "0.0.0", "dependencies": { + "@1shotapi/ows-oid4": "^0.1.0", + "@1shotapi/ows-signer": "^0.2.0", + "@1shotapi/ows-signer-utils": "^0.1.0", + "@1shotapi/ows-types": "^0.1.0", + "@1shotapi/ows-wallet-utils": "^0.1.0", "@fontsource-variable/geist": "^5.2.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -16,7 +21,8 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "shadcn": "^4.13.0", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "viem": "^2.55.2" }, "devDependencies": { "@ngrok/ngrok": "^1.7.0", @@ -32,6 +38,72 @@ "vite": "^7.0.4" } }, + "node_modules/@1shotapi/ows-oid4": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-oid4/-/ows-oid4-0.1.0.tgz", + "integrity": "sha512-V7TMOReu+zNoXrRK8NyyoW09wVK5UacFOYD/Zaz6jbT6xUvTZOtd/EzibnRtqfDTscCP8KPUszNrSD9MnnnSOA==", + "license": "MIT", + "dependencies": { + "@1shotapi/ows-types": "*", + "jose": "^6.0.11" + } + }, + "node_modules/@1shotapi/ows-signer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-signer/-/ows-signer-0.2.0.tgz", + "integrity": "sha512-RnN9703TrQMLjf3CXP+ZClz/plIDZ1n1mQNdxcLKGNDIZ3gp4TAV4vhML7ZL/+Sjo4u771qfSzYFjFKWWJCzDg==", + "license": "MIT" + }, + "node_modules/@1shotapi/ows-signer-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-signer-utils/-/ows-signer-utils-0.1.0.tgz", + "integrity": "sha512-tp5ho5F8rD4+bwC0IfFx/3Omo3amiQ1FAghp1OF2g6hiWxTA+a3sG4hnA4ieVACV9OP2H7jfPokqj0FpbJ+H6Q==", + "license": "MIT", + "dependencies": { + "@1shotapi/ows-types": "*", + "@scure/base": "^1.2.5" + }, + "peerDependencies": { + "viem": "^2.0.0" + } + }, + "node_modules/@1shotapi/ows-types": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-types/-/ows-types-0.1.0.tgz", + "integrity": "sha512-j9yEcmIGXHJpzuFA1r0VtRmmzLT9NcPLVTIQh1A/iPiIm/Pegcb44rWFClfusWezpzRpi1EtIAmSy4u/v4kcQA==", + "license": "MIT", + "dependencies": { + "@sd-jwt/core": "^0.20.0", + "@sd-jwt/sd-jwt-vc": "^0.20.0", + "ts-brand": "^0.2.0" + } + }, + "node_modules/@1shotapi/ows-wallet-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-wallet-utils/-/ows-wallet-utils-0.1.0.tgz", + "integrity": "sha512-tm0uLpQWu6fNvHIlNUy8JC88a3+i03sI2noV8XRL3RxZX73ulMzF3wYwS6w53vkZb6aRwQW4Mrp1Xr7jCcll+Q==", + "license": "MIT", + "dependencies": { + "@1shotapi/ows-types": "*", + "postmate": "^1.5.2", + "zod": "^4.4.0" + } + }, + "node_modules/@1shotapi/ows-wallet-utils/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -469,6 +541,84 @@ "node": ">=6.9.0" } }, + "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.2.tgz", + "integrity": "sha512-ZKZ/F8US7JR92J4DMct6cLW/Y66o2K576+zjlEN/MevH70bFIsB10wkZEQPLzl2oNh2SMGy55xpJ9JoBRl5DOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-darwin-x64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.2.tgz", + "integrity": "sha512-32b1mgc+P61Js+KW9VZv/c+xRw5EfmOcPx990JbCBSkYJFY0l25VinvyyWfl+3KjibQmAcYwmyzKF9J4DyKP/Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.2.tgz", + "integrity": "sha512-tNg0za41TpQfkhWjptD+0gSD2fggMiDCSacuIeELyb2xZhr7PrhPe5h66Jc67B/5dmpIhI2QOUtv4SBsricyYQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.2.tgz", + "integrity": "sha512-wfqgzqCAy/Vn8i6WVIh7qZd0DdBFaWBjPdB6ma+Wihcjv0gHqD/mw3ouVv7kbbUNrab6dKEx/w3xQZEdeXIlzg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-x64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.2.tgz", + "integrity": "sha512-rpiLnVEsqtPJ+mXTdx1rfz4RtUGYIUg2rUAZgd1KjiC1SehYUSkJN7Yh+aVfSjvCGtVP0/bfkQkXpPXKbmSUaA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-win32-x64": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.2.tgz", + "integrity": "sha512-dI+9P7cfWxkTQ+oE+7Aa6onEn92PHgfWXZivjNheCRmTBDBf2fx6RyTi0cmgpYLnD1KLZK9ZYrMxaPZ4oiXhGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@dotenvx/dotenvx": { "version": "1.75.1", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz", @@ -1491,6 +1641,45 @@ "node": ">= 10" } }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1526,6 +1715,23 @@ "node": ">= 8" } }, + "node_modules/@owf/identity-common": { + "version": "0.1.0-alpha-20260422101556", + "resolved": "https://registry.npmjs.org/@owf/identity-common/-/identity-common-0.1.0-alpha-20260422101556.tgz", + "integrity": "sha512-0CJhMHXWf+Dn0MpZCeachjFjMjBI2uCJl56Po3dnGiJDis5wVkfWPeCdPJZ2h4fBWwXV0+Owa8nJRxXeruRwrA==", + "license": "Apache-2.0" + }, + "node_modules/@owf/token-status-list": { + "version": "0.1.0-alpha-20260422101556", + "resolved": "https://registry.npmjs.org/@owf/token-status-list/-/token-status-list-0.1.0-alpha-20260422101556.tgz", + "integrity": "sha512-cwZHQPqBaXt3pTbllI6GLafE+Ws8HfEYoOgmpiQiK1sFElkF07mFZt1FTt1YElbMWtEV6YLTh+MjEWdJdRNklA==", + "license": "Apache-2.0", + "dependencies": { + "@owf/identity-common": "0.1.0-alpha-20260422101556", + "cbor-x": "^1.6.4", + "pako": "^2.1.0" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", @@ -3378,6 +3584,77 @@ "win32" ] }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sd-jwt/core": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@sd-jwt/core/-/core-0.20.0.tgz", + "integrity": "sha512-thTj5xtKqOvqnULELJGvCa64Hf+Hf7v0Dlao6mTh198rmZcyFCBop3vT1ShgWMhg5cL04ofXeiEhnQR10/8wjA==", + "license": "Apache-2.0", + "dependencies": { + "@owf/identity-common": "^0.1.0-alpha-20260312123226" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sd-jwt/sd-jwt-vc": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@sd-jwt/sd-jwt-vc/-/sd-jwt-vc-0.20.0.tgz", + "integrity": "sha512-V+pwRt4nRPDPnrHPRCbZubM3jL1Yn2DOT/AM/N/AawbFLWxsIf2HM7ioTrhpXRFC2kVFnWFmgNcq4mXXAzOBCA==", + "license": "Apache-2.0", + "dependencies": { + "@owf/token-status-list": "^0.1.0-alpha-20260312123226", + "@sd-jwt/core": "0.20.0", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sd-jwt/sd-jwt-vc/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -3788,6 +4065,27 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -4088,6 +4386,37 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cbor-extract": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.2.tgz", + "integrity": "sha512-hlSxxI9XO2yQfe9g6msd3g4xCfDqK5T5P0fRMLuaLHhxn4ViPrm+a+MUfhrvH2W962RGxcBwEGzLQyjbDG1gng==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.1.1" + }, + "bin": { + "download-cbor-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.2", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.2", + "@cbor-extract/cbor-extract-linux-arm": "2.2.2", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.2", + "@cbor-extract/cbor-extract-linux-x64": "2.2.2", + "@cbor-extract/cbor-extract-win32-x64": "2.2.2" + } + }, + "node_modules/cbor-x": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.4.tgz", + "integrity": "sha512-UGKHjp6RHC6QuZ2yy5LCKm7MojM4716DwoSaqwQpaH4DvZvbBTGcoDNTiG9Y2lByXZYFEs9WRkS5tLl96IrF1Q==", + "license": "MIT", + "optionalDependencies": { + "cbor-extract": "^2.2.2" + } + }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -4473,7 +4802,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -4717,6 +5046,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -5443,6 +5778,21 @@ "node": ">=18" } }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/jiti": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", @@ -6062,6 +6412,21 @@ "node": ">= 0.6" } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.51", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", @@ -6244,6 +6609,36 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/ox": { + "version": "0.14.30", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.30.tgz", + "integrity": "sha512-LI11uu+8iiM1B3CLckgd++YF1a0A2k5wDoM9ZeQMiL21BOzQs6L//BLS6hb1HSEKCyycdDIQLsVQx9MjpcC0hA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -6280,6 +6675,22 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz", + "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6445,6 +6856,12 @@ "node": ">=4" } }, + "node_modules/postmate": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/postmate/-/postmate-1.5.2.tgz", + "integrity": "sha512-EHLlEmrUA/hALls49oBrtE7BzDXXjB9EiO4MZpsoO3R/jRuBmD+2WKQuYAbeuVEpTzrPpUTT79z2cz4qaFgPRg==", + "license": "MIT" + }, "node_modules/powershell-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", @@ -7394,6 +7811,12 @@ "node": ">=0.6" } }, + "node_modules/ts-brand": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ts-brand/-/ts-brand-0.2.0.tgz", + "integrity": "sha512-H5uo7OqMvd91D2EefFmltBP9oeNInNzWLAZUSt6coGDn8b814Eis6SnEtzyXORr9ccEb38PfzyiRVDacdkycSQ==", + "license": "MIT" + }, "node_modules/ts-morph": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", @@ -7622,6 +8045,36 @@ "node": ">= 0.8" } }, + "node_modules/viem": { + "version": "2.55.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.55.2.tgz", + "integrity": "sha512-XlJeyNAZ96dQfOHlxLTK1FKgtWw/TtxENKNMBSBgxqALjiWiBWrFmSSzwwMivryKnBwkbt5E+90jSCLnVEilLA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.30", + "ws": "8.21.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/vite": { "version": "7.3.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", @@ -7718,6 +8171,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/wsl-utils": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", diff --git a/package.json b/package.json index 38aa3a2..07f8489 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,17 @@ "dev": "node scripts/dev.mjs", "dev:local": "node scripts/dev.mjs --no-tunnel", "build": "tsc -p tsconfig.json --noEmit && vite build && node scripts/copy-signer.mjs", - "sync:signer": "node scripts/sync-signer.mjs", "clean": "node scripts/clean.mjs", "lint": "tsc -p tsconfig.json --noEmit", "preview": "vite preview", "dockerize": "docker build -t oneshot-wallet ." }, "dependencies": { + "@1shotapi/ows-oid4": "^0.1.0", + "@1shotapi/ows-signer": "^0.2.0", + "@1shotapi/ows-signer-utils": "^0.1.0", + "@1shotapi/ows-types": "^0.1.0", + "@1shotapi/ows-wallet-utils": "^0.1.0", "@fontsource-variable/geist": "^5.2.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -23,7 +27,8 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "shadcn": "^4.13.0", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "viem": "^2.55.2" }, "devDependencies": { "@ngrok/ngrok": "^1.7.0", diff --git a/scripts/copy-signer.mjs b/scripts/copy-signer.mjs index b30790d..1e4a909 100644 --- a/scripts/copy-signer.mjs +++ b/scripts/copy-signer.mjs @@ -4,20 +4,23 @@ import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(__dirname, ".."); -const vendorSignerSrc = path.join(repoRoot, "vendor/ows-signer/src"); +const signerPkgSrc = path.join( + repoRoot, + "node_modules/@1shotapi/ows-signer/src", +); const signerPublic = path.join(repoRoot, "signer-static/index.html"); const outSigner = path.join(repoRoot, "dist/signer"); -if (!fs.existsSync(vendorSignerSrc)) { +if (!fs.existsSync(signerPkgSrc)) { console.error( - "vendor/ows-signer/src missing. Run: npm run sync:signer", + "node_modules/@1shotapi/ows-signer/src missing. Run: npm install", ); process.exit(1); } fs.rmSync(outSigner, { recursive: true, force: true }); fs.mkdirSync(path.join(outSigner, "src"), { recursive: true }); -fs.cpSync(vendorSignerSrc, path.join(outSigner, "src"), { recursive: true }); +fs.cpSync(signerPkgSrc, path.join(outSigner, "src"), { recursive: true }); fs.copyFileSync(signerPublic, path.join(outSigner, "index.html")); console.log("Copied Signing Layer to dist/signer/"); diff --git a/scripts/sync-signer.mjs b/scripts/sync-signer.mjs deleted file mode 100644 index 9c12195..0000000 --- a/scripts/sync-signer.mjs +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Refresh vendored @1shotapi/ows-signer from a local open-wallet / prf-wallet clone. - * - * Default source: ../prf-wallet/packages/ows-signer - * Override: OWS_SIGNER_SRC=/path/to/packages/ows-signer - */ -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const repoRoot = path.resolve(__dirname, ".."); -const defaultSrc = path.resolve(repoRoot, "../prf-wallet/packages/ows-signer"); -const srcRoot = process.env.OWS_SIGNER_SRC - ? path.resolve(process.env.OWS_SIGNER_SRC) - : defaultSrc; -const destRoot = path.join(repoRoot, "vendor/ows-signer"); -const srcDir = path.join(srcRoot, "src"); - -if (!fs.existsSync(srcDir)) { - console.error( - `ows-signer source not found at ${srcDir}\n` + - `Set OWS_SIGNER_SRC to packages/ows-signer (default: ${defaultSrc})`, - ); - process.exit(1); -} - -fs.rmSync(destRoot, { recursive: true, force: true }); -fs.mkdirSync(destRoot, { recursive: true }); -fs.cpSync(srcDir, path.join(destRoot, "src"), { recursive: true }); - -const pkgJson = path.join(srcRoot, "package.json"); -if (fs.existsSync(pkgJson)) { - fs.copyFileSync(pkgJson, path.join(destRoot, "package.json")); -} - -console.log(`Synced ows-signer src → vendor/ows-signer/ (from ${srcRoot})`); diff --git a/src/App.tsx b/src/App.tsx index 0f3f563..6a1319d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,35 @@ -import { Button } from "@/components/ui/button"; +import { useWallet } from "./wallet/WalletProvider"; +import { WalletChrome } from "./components/WalletChrome"; +import { OnboardingPanel } from "./components/OnboardingPanel"; +import { MainPanel } from "./components/MainPanel"; +import { SignerHost } from "./components/SignerHost"; +import { ModalHost } from "./components/ModalHost"; export function App() { + const { bootError, ready, unlocked, walletCreated, embedded } = useWallet(); + + const showOnboarding = embedded && !walletCreated && !unlocked; + return ( -
-

1Shot Wallet

-

- Branding Layer bootstrapping — OWS wiring comes next. -

- +
+ {embedded ? : null} + +
+ {bootError ? ( +

+ Failed to start: {bootError} +

+ ) : !ready ? ( +

Loading…

+ ) : showOnboarding ? ( + + ) : ( + + )} +
+ + +
); } diff --git a/src/components/MainPanel.tsx b/src/components/MainPanel.tsx new file mode 100644 index 0000000..9e35611 --- /dev/null +++ b/src/components/MainPanel.tsx @@ -0,0 +1,116 @@ +import { useWallet } from "../wallet/WalletProvider"; + +export function MainPanel() { + const { + unlocked, + walletCreated, + evmAddress, + solanaAddress, + chainId, + chains, + credentialCount, + switchChain, + openCreateBackup, + openRestoreBackup, + openCredentialList, + } = useWallet(); + + const status = unlocked + ? "Unlocked" + : walletCreated + ? "Created (locked)" + : "Not created"; + + return ( + <> +

OWS Example General Wallet

+

+ Branding Layer — general-purpose wallet with nested Signing Layer. +

+ +
+
+ Status + {status} +
+
+ EVM + {evmAddress} +
+
+ Solana + + {solanaAddress} + +
+
+ + +
+
+ Credentials + {credentialCount} +
+ +
+ {unlocked ? ( + + ) : null} + {!unlocked ? ( + + ) : null} + +
+
+ + ); +} diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx new file mode 100644 index 0000000..1649721 --- /dev/null +++ b/src/components/Modal.tsx @@ -0,0 +1,100 @@ +import type { ReactNode } from "react"; +import { useEffect, useId, useRef } from "react"; + +export type ModalAction = { + label: string; + onClick: () => void; + variant?: "primary" | "secondary"; + autoFocus?: boolean; + disabled?: boolean; +}; + +export type ModalProps = { + title: string; + children: ReactNode; + actions?: ModalAction[]; + onBackdropDismiss?: () => void; + /** Extra content after children (e.g. signer slot). */ + footer?: ReactNode; + wide?: boolean; +}; + +export function Modal({ + title, + children, + actions, + onBackdropDismiss, + footer, + wide, +}: ModalProps) { + const titleId = useId(); + const panelRef = useRef(null); + + useEffect(() => { + const focusTarget = + panelRef.current?.querySelector("[data-autofocus]") ?? + panelRef.current?.querySelector( + "button, [href], input, select, textarea", + ); + focusTarget?.focus(); + }, []); + + useEffect(() => { + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape" && onBackdropDismiss) { + event.preventDefault(); + onBackdropDismiss(); + } + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [onBackdropDismiss]); + + return ( +
{ + if (event.target === event.currentTarget) { + onBackdropDismiss?.(); + } + }} + > +
+

+ {title} +

+
{children}
+ {footer} + {actions && actions.length > 0 ? ( +
+ {actions.map((action) => ( + + ))} +
+ ) : null} +
+
+ ); +} diff --git a/src/components/ModalHost.tsx b/src/components/ModalHost.tsx new file mode 100644 index 0000000..e927150 --- /dev/null +++ b/src/components/ModalHost.tsx @@ -0,0 +1,79 @@ +import { useWallet } from "../wallet/WalletProvider"; +import { + ConnectModal, + PasskeyNameModal, + WalletSetupModal, +} from "./modals/SetupModals"; +import { PersonalSignModal, TypedDataModal } from "./modals/SignModals"; +import { + CredentialListModal, + CredentialOfferModal, + CredentialPresentationModal, +} from "./modals/CredentialModals"; +import { CreateBackupModal, RestoreBackupModal } from "./modals/BackupModals"; + +export function ModalHost() { + const { activeModal } = useWallet(); + if (!activeModal) return null; + + switch (activeModal.kind) { + case "walletSetup": + return ; + case "passkeyName": + return ; + case "connect": + return ; + case "personalSign": + return ( + + ); + case "typedData": + return ( + + ); + case "credentialOffer": + return ( + + ); + case "credentialPresentation": + return ( + + ); + case "credentialList": + return ( + + ); + case "createBackup": + return ( + + ); + case "restoreBackup": + return ( + + ); + default: + return null; + } +} diff --git a/src/components/OnboardingPanel.tsx b/src/components/OnboardingPanel.tsx new file mode 100644 index 0000000..0b3cc29 --- /dev/null +++ b/src/components/OnboardingPanel.tsx @@ -0,0 +1,39 @@ +import { useWallet } from "../wallet/WalletProvider"; + +export function OnboardingPanel() { + const { loginWithPasskey, createNewWalletFromUi } = useWallet(); + + return ( +
+

Welcome

+

+ Log in with an existing passkey or create a new wallet account to get + started. +

+
+ + +
+
+ ); +} diff --git a/src/components/SignerHost.tsx b/src/components/SignerHost.tsx new file mode 100644 index 0000000..2bc1eda --- /dev/null +++ b/src/components/SignerHost.tsx @@ -0,0 +1,7 @@ +import { useWallet } from "../wallet/WalletProvider"; + +/** Hidden mount point for the Signing Layer iframe. */ +export function SignerHost() { + const { signerContainerRef } = useWallet(); + return
; +} diff --git a/src/components/WalletChrome.tsx b/src/components/WalletChrome.tsx new file mode 100644 index 0000000..fdfed12 --- /dev/null +++ b/src/components/WalletChrome.tsx @@ -0,0 +1,24 @@ +import { useWallet } from "../wallet/WalletProvider"; + +export function WalletChrome() { + const { requestHide } = useWallet(); + + return ( +
+

Open Wallet

+ +
+ ); +} diff --git a/src/components/modals/BackupModals.tsx b/src/components/modals/BackupModals.tsx new file mode 100644 index 0000000..27e6613 --- /dev/null +++ b/src/components/modals/BackupModals.tsx @@ -0,0 +1,351 @@ +import { useEffect, useRef, useState } from "react"; +import { overlaySignerIframe } from "@1shotapi/ows-signer-utils"; +import type { RecoveryDataCreatedData } from "@1shotapi/ows-types"; +import { Modal } from "../Modal"; +import { useWallet } from "../../wallet/WalletProvider"; + +const DEFAULT_MIN_PASSWORD_LENGTH = 12; + +function waitForPaint(): Promise { + return new Promise((resolve) => { + requestAnimationFrame(() => { + requestAnimationFrame(() => resolve()); + }); + }); +} + +function formatBackupError(error: unknown): string { + if (error instanceof Error) { + const message = error.message; + if (message.includes("passwordTooShort")) { + return "Passphrase is too short. Try again."; + } + if (message.includes("NotAllowed") || message.includes("not allowed")) { + return "Passkey prompt was cancelled or blocked."; + } + return message || "Backup failed."; + } + return "Backup failed."; +} + +export function CreateBackupModal({ + onResolve, + onReject, +}: { + onResolve: () => void; + onReject: (error: unknown) => void; +}) { + const { getSigner, signerContainerRef, ensureReady, persistBackup } = + useWallet(); + const signerSlotRef = useRef(null); + const [phase, setPhase] = useState<"prompt" | "result" | "error">("prompt"); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + const [copyLabel, setCopyLabel] = useState("Copy"); + const abortedRef = useRef(false); + const restoreOverlayRef = useRef<(() => void) | null>(null); + + useEffect(() => { + abortedRef.current = false; + const home = signerContainerRef.current; + const slot = signerSlotRef.current; + if (!home || !slot) { + onReject(new Error("Signer not ready for backup")); + return; + } + + void (async () => { + try { + // Unlock is usually a no-op here (create only when unlocked); this still + // waits for Signing Layer load before getSigner(). + await ensureReady(); + if (abortedRef.current) return; + + const signer = getSigner(); + if (!signer) { + throw new Error("Signer not ready for backup"); + } + + const iframe = home.querySelector("iframe"); + if (!(iframe instanceof HTMLIFrameElement)) { + throw new Error("Signer iframe not found in signerContainer"); + } + + restoreOverlayRef.current = overlaySignerIframe(iframe, slot, { + homeContainer: home, + }); + await waitForPaint(); + + const created = await signer.createRecoveryData( + `Passphrase (min ${DEFAULT_MIN_PASSWORD_LENGTH} characters)`, + "Continue", + DEFAULT_MIN_PASSWORD_LENGTH, + ); + + if (abortedRef.current) return; + + restoreOverlayRef.current?.(); + restoreOverlayRef.current = null; + + await persistBackup(created.encryptedPrivateKey); + if (abortedRef.current) return; + + setResult(created); + setPhase("result"); + } catch (err) { + if (abortedRef.current) return; + restoreOverlayRef.current?.(); + restoreOverlayRef.current = null; + setError(formatBackupError(err)); + setPhase("error"); + } + })().catch((err: unknown) => { + if (abortedRef.current) return; + restoreOverlayRef.current?.(); + restoreOverlayRef.current = null; + onReject(err); + }); + + return () => { + abortedRef.current = true; + restoreOverlayRef.current?.(); + restoreOverlayRef.current = null; + }; + }, [ + ensureReady, + getSigner, + onReject, + persistBackup, + signerContainerRef, + ]); + + if (phase === "result" && result) { + return ( + { + void navigator.clipboard.writeText(result.encryptedPrivateKey).then( + () => { + setCopyLabel("Copied"); + setTimeout(() => setCopyLabel("Copy"), 1500); + }, + () => setCopyLabel("Copy failed"), + ); + }, + }, + { + label: "Done", + variant: "primary", + autoFocus: true, + onClick: onResolve, + }, + ]} + > +

+ Encrypted backup +

+
+          {result.encryptedPrivateKey}
+        
+
+ ); + } + + return ( + + ) : null + } + actions={ + phase === "error" || phase === "prompt" + ? [ + { + label: phase === "error" ? "Close" : "Cancel", + variant: "secondary", + autoFocus: phase === "error", + onClick: onResolve, + }, + ] + : undefined + } + > + {phase === "prompt" ? ( +

+ Enter a passphrase of at least {DEFAULT_MIN_PASSWORD_LENGTH}{" "} + characters to encrypt your private key. Store the backup somewhere + safe — you will need it to restore your wallet. +

+ ) : null} + {error ? ( +

+ {error} +

+ ) : null} +
+ ); +} + +function formatRestoreError(error: unknown): string { + if (error instanceof Error) { + const message = error.message; + if ( + message.includes("decryptionFailed") || + message.includes("decrypt") || + message.includes("OperationError") + ) { + return "Could not decrypt the backup. Check the passphrase and try again."; + } + if (message.includes("NotAllowed") || message.includes("not allowed")) { + return "Passkey prompt was cancelled or blocked."; + } + return message || "Restore failed."; + } + return "Restore failed."; +} + +export function RestoreBackupModal({ + encryptedPrivateKey, + onResolve, + onReject, +}: { + encryptedPrivateKey: string; + onResolve: (restored: boolean) => void; + onReject: (error: unknown) => void; +}) { + const { getSigner, signerContainerRef, awaitSignerReady } = useWallet(); + const signerSlotRef = useRef(null); + const [phase, setPhase] = useState<"prompt" | "done" | "error">("prompt"); + const [error, setError] = useState(null); + const abortedRef = useRef(false); + const restoreOverlayRef = useRef<(() => void) | null>(null); + + useEffect(() => { + abortedRef.current = false; + const home = signerContainerRef.current; + const slot = signerSlotRef.current; + if (!home || !slot) { + onReject(new Error("Signer not ready for restore")); + return; + } + + void (async () => { + try { + // Restore is offered while locked — wait for Signing Layer only. + // Do not call ensureReady() (that would unlock / run setup first). + await awaitSignerReady(); + if (abortedRef.current) return; + + const signer = getSigner(); + if (!signer) { + throw new Error("Signer not ready for restore"); + } + + const iframe = home.querySelector("iframe"); + if (!(iframe instanceof HTMLIFrameElement)) { + throw new Error("Signer iframe not found in signerContainer"); + } + + restoreOverlayRef.current = overlaySignerIframe(iframe, slot, { + homeContainer: home, + }); + await waitForPaint(); + await signer.recoverKey( + encryptedPrivateKey, + "Backup passphrase", + "Restore", + ); + if (abortedRef.current) return; + restoreOverlayRef.current?.(); + restoreOverlayRef.current = null; + setPhase("done"); + } catch (err) { + if (abortedRef.current) return; + restoreOverlayRef.current?.(); + restoreOverlayRef.current = null; + setError(formatRestoreError(err)); + setPhase("error"); + } + })().catch((err: unknown) => { + if (abortedRef.current) return; + restoreOverlayRef.current?.(); + restoreOverlayRef.current = null; + onReject(err); + }); + + return () => { + abortedRef.current = true; + restoreOverlayRef.current?.(); + restoreOverlayRef.current = null; + }; + }, [ + awaitSignerReady, + encryptedPrivateKey, + getSigner, + onReject, + signerContainerRef, + ]); + + return ( + onResolve(false) : undefined + } + footer={ + phase === "prompt" ? ( +
+ ) : null + } + actions={ + phase === "done" + ? [ + { + label: "Done", + variant: "primary", + autoFocus: true, + onClick: () => onResolve(true), + }, + ] + : [ + { + label: phase === "error" ? "Close" : "Cancel", + variant: "secondary", + autoFocus: phase === "error", + onClick: () => onResolve(false), + }, + ] + } + > + {phase === "prompt" ? ( +

+ Enter the passphrase you used when creating this backup to unlock your + wallet. +

+ ) : null} + {phase === "done" ? ( +

+ Wallet restored. You can sign until this tab is closed. +

+ ) : null} + {error ? ( +

+ {error} +

+ ) : null} + + ); +} diff --git a/src/components/modals/CredentialModals.tsx b/src/components/modals/CredentialModals.tsx new file mode 100644 index 0000000..f0a808c --- /dev/null +++ b/src/components/modals/CredentialModals.tsx @@ -0,0 +1,147 @@ +import type { + CredentialOfferApprovalRequest, + CredentialPresentationApprovalRequest, + CredentialSummary, +} from "@1shotapi/ows-types"; +import { Modal } from "../Modal"; + +export function CredentialOfferModal({ + request, + onResolve, +}: { + request: CredentialOfferApprovalRequest; + onResolve: (approved: boolean) => void; +}) { + return ( + onResolve(false)} + actions={[ + { + label: "Reject", + variant: "secondary", + onClick: () => onResolve(false), + }, + { + label: "Accept", + variant: "primary", + autoFocus: true, + onClick: () => onResolve(true), + }, + ]} + > +

+ {request.issuerName} ({request.issuerId}) wants to issue a credential to + your wallet. +

+

Offered credentials:

+
    + {request.offeredCredentials.map((credential) => { + const scope = credential.scope ? ` — ${credential.scope}` : ""; + return ( +
  • + {credential.configurationId} ({credential.format}) + {scope} +
  • + ); + })} +
+

+ You may be asked to verify with your passkey after you continue. +

+
+ ); +} + +export function CredentialPresentationModal({ + request, + onResolve, +}: { + request: CredentialPresentationApprovalRequest; + onResolve: (approved: boolean) => void; +}) { + return ( + onResolve(false)} + actions={[ + { + label: "Reject", + variant: "secondary", + onClick: () => onResolve(false), + }, + { + label: "Share", + variant: "primary", + autoFocus: true, + onClick: () => onResolve(true), + }, + ]} + > +

+ {request.verifierName} ({request.verifierId}) is requesting proof. +

+

+ Credential: {request.credentialType} from {request.credentialIssuer} +

+

Claims to disclose:

+
    + {request.requestedClaims.map((claim) => ( +
  • {claim}
  • + ))} +
+

+ You may be asked to verify with your passkey after you continue. +

+
+ ); +} + +export function CredentialListModal({ + credentials, + onResolve, +}: { + credentials: CredentialSummary[]; + onResolve: () => void; +}) { + return ( + + {credentials.length === 0 ? ( +

+ No credentials stored in this wallet yet. +

+ ) : ( +
    + {credentials.map((credential) => ( +
  • +

    + {credential.type.join(", ")} +

    +

    Issuer: {credential.issuer}

    +

    + {credential.validUntil + ? `Issued ${credential.issuedAt} · Valid until ${credential.validUntil}` + : `Issued ${credential.issuedAt}`} +

    +
  • + ))} +
+ )} +
+ ); +} diff --git a/src/components/modals/SetupModals.tsx b/src/components/modals/SetupModals.tsx new file mode 100644 index 0000000..0fd6a34 --- /dev/null +++ b/src/components/modals/SetupModals.tsx @@ -0,0 +1,136 @@ +import { useState } from "react"; +import { Modal } from "../Modal"; +import type { WalletSetupChoice } from "../../wallet/modalTypes"; + +export function WalletSetupModal({ + onResolve, +}: { + onResolve: (choice: WalletSetupChoice) => void; +}) { + return ( + onResolve("cancel")} + actions={[ + { + label: "Cancel", + variant: "secondary", + onClick: () => onResolve("cancel"), + }, + { + label: "Login with passkey", + variant: "primary", + autoFocus: true, + onClick: () => onResolve("login"), + }, + { + label: "Create account", + variant: "primary", + onClick: () => onResolve("create"), + }, + ]} + > +

+ This wallet uses a passkey to secure your keys on this device. Log in + with an existing passkey or create a new account before continuing. +

+
+ ); +} + +export function PasskeyNameModal({ + onResolve, +}: { + onResolve: (name: string | null) => void; +}) { + const [name, setName] = useState(""); + const [error, setError] = useState(null); + + const submit = () => { + const trimmed = name.trim(); + if (!trimmed) { + setError("Enter a name for your passkey."); + return; + } + onResolve(trimmed); + }; + + return ( + onResolve(null)} + actions={[ + { + label: "Cancel", + variant: "secondary", + onClick: () => onResolve(null), + }, + { + label: "Continue", + variant: "primary", + onClick: submit, + }, + ]} + > +

+ Choose a name for this wallet passkey. Your device will use it when you + create the credential and when you sign in later. +

+ + { + setName(event.target.value); + setError(null); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + submit(); + } + }} + className="mb-2 box-border w-full rounded-md border border-[color-mix(in_srgb,CanvasText_25%,transparent)] bg-[Canvas] px-2.5 py-2 text-inherit" + /> + {error ?

{error}

: null} +
+ ); +} + +export function ConnectModal({ + onResolve, +}: { + onResolve: (approved: boolean) => void; +}) { + return ( + onResolve(false)} + actions={[ + { + label: "Reject", + variant: "secondary", + onClick: () => onResolve(false), + }, + { + label: "Continue", + variant: "primary", + autoFocus: true, + onClick: () => onResolve(true), + }, + ]} + > +

+ The connected app is requesting your wallet address. You may be asked to + verify with your passkey after you continue. +

+
+ ); +} diff --git a/src/components/modals/SignModals.tsx b/src/components/modals/SignModals.tsx new file mode 100644 index 0000000..ccf144b --- /dev/null +++ b/src/components/modals/SignModals.tsx @@ -0,0 +1,123 @@ +import type { + PersonalSignApprovalRequest, + SignTypedDataApprovalRequest, +} from "@1shotapi/ows-signer-utils"; +import { ConversionUtils, HexString } from "@1shotapi/ows-types"; +import { Modal } from "../Modal"; + +export function PersonalSignModal({ + request, + onResolve, +}: { + request: PersonalSignApprovalRequest; + onResolve: (approved: boolean) => void; +}) { + return ( + onResolve(false)} + actions={[ + { + label: "Reject", + variant: "secondary", + onClick: () => onResolve(false), + }, + { + label: "Sign", + variant: "primary", + autoFocus: true, + onClick: () => onResolve(true), + }, + ]} + > +

Account

+

{request.address}

+

Message

+
+        {formatMessageForDisplay(request.message)}
+      
+
+ ); +} + +export function TypedDataModal({ + request, + onResolve, +}: { + request: SignTypedDataApprovalRequest; + onResolve: (approved: boolean) => void; +}) { + const { typedData } = request; + return ( + onResolve(false)} + actions={[ + { + label: "Reject", + variant: "secondary", + onClick: () => onResolve(false), + }, + { + label: "Sign", + variant: "primary", + autoFocus: true, + onClick: () => onResolve(true), + }, + ]} + > +

Account

+

{request.address}

+ + + +
+ ); +} + +function LabeledBlock({ label, content }: { label: string; content: string }) { + return ( +
+

{label}

+
+        {content}
+      
+
+ ); +} + +function formatJson(value: unknown): string { + try { + return JSON.stringify(value, (_key, v) => + typeof v === "bigint" ? v.toString() : v, + 2, + ); + } catch { + return String(value); + } +} + +function formatMessageForDisplay(message: string): string { + if (message.startsWith("0x") && message.length > 2) { + try { + const bytes = ConversionUtils.hexToBytes(HexString(message as `0x${string}`)); + const decoded = new TextDecoder("utf-8", { fatal: false }).decode(bytes); + if (isMostlyPrintable(decoded)) { + return decoded; + } + } catch { + // fall through + } + } + return message; +} + +function isMostlyPrintable(text: string): boolean { + if (!text.trim()) return false; + let printable = 0; + for (const char of text) { + const code = char.codePointAt(0) ?? 0; + if (code >= 32 && code !== 127) printable++; + } + return printable / text.length >= 0.85; +} diff --git a/src/demo/demo-keys.ts b/src/demo/demo-keys.ts new file mode 100644 index 0000000..7261bb3 --- /dev/null +++ b/src/demo/demo-keys.ts @@ -0,0 +1,10 @@ +/** + * MOCK demo Ed25519 holder key — TEST ONLY. Do not use in production. + * Used by DemoWalletAttestationProvider until a wallet-backed IHolderSigner is wired. + */ +export const DEMO_HOLDER_PRIVATE_JWK: JsonWebKey = { + crv: "Ed25519", + d: "G8_8M9RjEA5L5NIeAoB24C9yOb8KPuRoh9y7SidoXJA", + x: "3dmWOAMTtkMxm88aJ9QhlK5SWimNXt6-WTsI4eFHwC8", + kty: "OKP", +}; diff --git a/src/demo/in-memory-trust-registry.ts b/src/demo/in-memory-trust-registry.ts new file mode 100644 index 0000000..a35037b --- /dev/null +++ b/src/demo/in-memory-trust-registry.ts @@ -0,0 +1,72 @@ +import { + CredentialIssuer, + type IIssuerTrustRegistry, + type IssuerTrustMetadata, +} from "@1shotapi/ows-types"; + +/** MOCK KYC issuer — demo trust entry until production allow-lists land. */ +export const MOCK_KYC_ISSUER_ID = CredentialIssuer( + "https://kyc.demo.issuer.example", +); + +const MOCK_ISSUER_TRUST: IssuerTrustMetadata = { + issuerId: MOCK_KYC_ISSUER_ID, + name: "Demo KYC Issuer", + assuranceLevels: ["substantial", "high"], + jurisdictions: ["US", "GB"], +}; + +const LOCAL_DEMO_HOSTNAMES = new Set([ + "localhost", + "127.0.0.1", + "ows-host.com", + "ows-issuer.com", + "ows-verifier.com", +]); + +/** True for local Vite / mkcert demo issuer origins. */ +export function isLocalDemoIssuerOrigin(issuerId: string): boolean { + try { + const url = new URL(issuerId); + return ( + LOCAL_DEMO_HOSTNAMES.has(url.hostname) || + url.hostname.endsWith(".ows-host.com") || + url.hostname.endsWith(".ows-issuer.com") || + url.hostname.endsWith(".ows-verifier.com") + ); + } catch { + return false; + } +} + +export class InMemoryIssuerTrustRegistry implements IIssuerTrustRegistry { + constructor( + private readonly entries: IssuerTrustMetadata[] = [MOCK_ISSUER_TRUST], + ) {} + + async isTrustedIssuer(issuerId: CredentialIssuer): Promise { + const id = String(issuerId); + if (isLocalDemoIssuerOrigin(id)) return true; + return this.entries.some((e) => e.issuerId === issuerId); + } + + async resolveIssuer( + issuerId: CredentialIssuer, + ): Promise { + const found = this.entries.find((e) => e.issuerId === issuerId); + if (found) return found; + if (isLocalDemoIssuerOrigin(String(issuerId))) { + return { + issuerId, + name: "Local Demo KYC Issuer", + assuranceLevels: ["substantial", "high"], + jurisdictions: ["US", "GB"], + }; + } + return undefined; + } + + async listIssuers(): Promise { + return [...this.entries]; + } +} diff --git a/src/demo/local-storage-store.ts b/src/demo/local-storage-store.ts new file mode 100644 index 0000000..0b217ae --- /dev/null +++ b/src/demo/local-storage-store.ts @@ -0,0 +1,159 @@ +import type { CredentialId } from "@1shotapi/ows-types"; +import type { + ICredentialRepository, + StoredCredential, + CredentialFilter, + CredentialSummary, +} from "@1shotapi/ows-types"; + +/** localStorage key for demo credentials (shared across wallet iframe loads on one origin). */ +export const OWS_MOCK_CREDENTIALS_STORAGE_KEY = "ows.mock.credentials.v1"; + +/** Minimal sync key/value API (localStorage or test double). */ +export type CredentialStorageBackend = { + getItem(key: string): string | null; + setItem(key: string, value: string): void; + removeItem(key: string): void; +}; + +type StoredBlob = { + credentials: Record; + revoked: string[]; +}; + +function summarize( + credentials: Iterable, + filter?: CredentialFilter, +): CredentialSummary[] { + const filtered = [...credentials].filter((c) => { + if (filter?.type && !c.type.includes(filter.type)) { + return false; + } + if (filter?.issuer && c.issuer !== filter.issuer) { + return false; + } + return true; + }); + + return filtered.map((c) => ({ + credentialId: c.credentialId, + type: c.type, + issuer: c.issuer, + format: c.format, + issuedAt: c.issuedAt, + validUntil: c.validUntil, + })); +} + +/** Persists credentials in localStorage (plaintext — demo / bootstrap only). */ +export class LocalStorageCredentialRepository implements ICredentialRepository { + private readonly storageKey: string; + private readonly storage: CredentialStorageBackend; + + constructor( + options: { + storageKey?: string; + storage?: CredentialStorageBackend; + } = {}, + ) { + this.storageKey = + options.storageKey ?? OWS_MOCK_CREDENTIALS_STORAGE_KEY; + this.storage = + options.storage ?? + (typeof localStorage !== "undefined" + ? localStorage + : createMemoryStorageBackend()); + } + + async store(credential: StoredCredential): Promise { + const blob = this.readBlob(); + blob.credentials[credential.credentialId] = credential; + blob.revoked = blob.revoked.filter((id) => id !== credential.credentialId); + this.writeBlob(blob); + } + + async get(credentialId: CredentialId): Promise { + const blob = this.readBlob(); + if (blob.revoked.includes(credentialId)) { + return undefined; + } + return blob.credentials[credentialId]; + } + + async list(filter?: CredentialFilter): Promise { + const blob = this.readBlob(); + const active = Object.values(blob.credentials).filter( + (c) => !blob.revoked.includes(c.credentialId), + ); + return summarize(active, filter); + } + + async delete(credentialId: CredentialId): Promise { + const blob = this.readBlob(); + delete blob.credentials[credentialId]; + this.writeBlob(blob); + } + + async revoke(credentialId: CredentialId): Promise { + const blob = this.readBlob(); + if (blob.credentials[credentialId] && !blob.revoked.includes(credentialId)) { + blob.revoked.push(credentialId); + this.writeBlob(blob); + } + } + + private readBlob(): StoredBlob { + const raw = this.storage.getItem(this.storageKey); + if (!raw) { + return { credentials: {}, revoked: [] }; + } + + try { + const parsed = JSON.parse(raw) as + | StoredBlob + | Record; + if ( + parsed && + typeof parsed === "object" && + "credentials" in parsed && + typeof (parsed as StoredBlob).credentials === "object" + ) { + const blob = parsed as StoredBlob; + return { + credentials: blob.credentials ?? {}, + revoked: Array.isArray(blob.revoked) ? blob.revoked : [], + }; + } + return { + credentials: parsed as Record, + revoked: [], + }; + } catch { + return { credentials: {}, revoked: [] }; + } + } + + private writeBlob(blob: StoredBlob): void { + const credCount = Object.keys(blob.credentials).length; + if (credCount === 0 && blob.revoked.length === 0) { + this.storage.removeItem(this.storageKey); + return; + } + + this.storage.setItem(this.storageKey, JSON.stringify(blob)); + } +} + +/** In-memory backend for Node tests and SSR fallbacks. */ +export function createMemoryStorageBackend(): CredentialStorageBackend { + const data = new Map(); + return { + getItem: (key) => data.get(key) ?? null, + setItem: (key, value) => { + data.set(key, value); + }, + removeItem: (key) => { + data.delete(key); + }, + }; +} diff --git a/src/index.css b/src/index.css index fb3c7e9..12aa1a2 100644 --- a/src/index.css +++ b/src/index.css @@ -1,130 +1,27 @@ @import "tailwindcss"; -@import "tw-animate-css"; -@import "shadcn/tailwind.css"; -@import "@fontsource-variable/geist"; -@custom-variant dark (&:is(.dark *)); - -@theme inline { - --font-heading: var(--font-sans); - --font-sans: 'Geist Variable', sans-serif; - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); - --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); - --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); - --color-foreground: var(--foreground); - --color-background: var(--background); - --radius-sm: calc(var(--radius) * 0.6); - --radius-md: calc(var(--radius) * 0.8); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) * 1.4); - --radius-2xl: calc(var(--radius) * 1.8); - --radius-3xl: calc(var(--radius) * 2.2); - --radius-4xl: calc(var(--radius) * 2.6); +:root { + color-scheme: light dark; } -:root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); +html, +body, +#root { + margin: 0; + min-height: 100%; } -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); +body { + font-family: system-ui, sans-serif; + line-height: 1.5; + background: Canvas; + color: CanvasText; } -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } - html { - @apply font-sans; - } -} \ No newline at end of file +#signer-container { + position: absolute; + width: 0; + height: 0; + overflow: hidden; + clip-path: inset(50%); +} diff --git a/src/main.tsx b/src/main.tsx index d4824f1..5ba93f0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,6 @@ import { createRoot } from "react-dom/client"; import { App } from "./App"; +import { WalletProvider } from "./wallet/WalletProvider"; import "./index.css"; const root = document.getElementById("root"); @@ -7,4 +8,8 @@ if (!root) { throw new Error("#root not found"); } -createRoot(root).render(); +createRoot(root).render( + + + , +); diff --git a/src/ows/demoChains.ts b/src/ows/demoChains.ts new file mode 100644 index 0000000..7047d0e --- /dev/null +++ b/src/ows/demoChains.ts @@ -0,0 +1,24 @@ +import { EVMChainId } from "@1shotapi/ows-types"; + +/** Demo chains for the branding-layer chain dropdown (fed into RpcHelper). */ +export const DEMO_CHAINS: ReadonlyArray<{ + chainId: EVMChainId; + label: string; + rpcUrl: string; +}> = [ + { + chainId: EVMChainId("0xaa36a7"), // 11155111 + label: "Sepolia", + rpcUrl: "https://sepolia.drpc.org", + }, + { + chainId: EVMChainId("0x14a34"), // 84532 + label: "Base Sepolia", + rpcUrl: "https://sepolia.base.org", + }, + { + chainId: EVMChainId("0x4cef52"), // 5042002 + label: "Arc Testnet", + rpcUrl: "https://rpc.testnet.arc.network", + }, +]; diff --git a/src/ows/registerAccountConnect.ts b/src/ows/registerAccountConnect.ts new file mode 100644 index 0000000..7ee2359 --- /dev/null +++ b/src/ows/registerAccountConnect.ts @@ -0,0 +1,63 @@ +import type { OWSSigner } from "@1shotapi/ows-signer-utils"; +import type { OWSWallet } from "@1shotapi/ows-wallet-utils"; +import { + EVMAccountAddress, + OwsUserRejectedError, + type SolanaAccountAddress, +} from "@1shotapi/ows-types"; + +export type AccountConnectStorage = { + loadCachedEvmAddress: () => EVMAccountAddress | undefined; + saveCachedAddresses: ( + evm: EVMAccountAddress, + solana?: SolanaAccountAddress, + ) => void; +}; + +export type RegisterAccountConnectOptions = { + storage: AccountConnectStorage; + ensureReady: () => Promise; + requestConnectApproval: () => Promise; +}; + +/** + * Register `eth_accounts` / `eth_requestAccounts` on the wallet (pre-`start()`). + */ +export function registerAccountConnect( + wallet: OWSWallet, + signer: OWSSigner, + options: RegisterAccountConnectOptions, +): void { + wallet.registerEip1193("eth_accounts", async () => { + const cached = options.storage.loadCachedEvmAddress(); + if (cached) { + return [cached]; + } + return []; + }); + + wallet.registerEip1193("eth_requestAccounts", async () => { + const cached = options.storage.loadCachedEvmAddress(); + if (cached) { + return [cached]; + } + + const display = await wallet.requestDisplay({ width: 420, height: 360 }); + try { + const approved = await options.requestConnectApproval(); + if (!approved) { + throw new OwsUserRejectedError( + "User rejected the account connection request", + ); + } + + await options.ensureReady(); + const evm = await signer.evm.getAccountAddress(); + const solana = await signer.solana.getAccountAddress(); + options.storage.saveCachedAddresses(evm, solana); + return [evm]; + } finally { + await display.hide(); + } + }); +} diff --git a/src/ows/registerApprovalSigning.ts b/src/ows/registerApprovalSigning.ts new file mode 100644 index 0000000..0e0a6a9 --- /dev/null +++ b/src/ows/registerApprovalSigning.ts @@ -0,0 +1,38 @@ +import { SignHelper } from "@1shotapi/ows-signer-utils"; +import type { + OWSSigner, + PersonalSignApprovalRequest, + SignTypedDataApprovalRequest, +} from "@1shotapi/ows-signer-utils"; +import type { OWSWallet } from "@1shotapi/ows-wallet-utils"; + +export type RegisterApprovalSigningOptions = { + ensureReady?: () => Promise; + requestPersonalSignApproval: ( + request: PersonalSignApprovalRequest, + ) => Promise; + requestSignTypedDataApproval: ( + request: SignTypedDataApprovalRequest, + ) => Promise; +}; + +/** + * Build SignHelper handlers and register them on the wallet (pre-`start()`). + */ +export function registerApprovalSigning( + wallet: OWSWallet, + signer: OWSSigner, + options: RegisterApprovalSigningOptions, +): SignHelper { + const helper = new SignHelper(signer, wallet, { + ensureReady: options.ensureReady, + requestPersonalSignApproval: options.requestPersonalSignApproval, + requestSignTypedDataApproval: options.requestSignTypedDataApproval, + }); + + for (const [method, handler] of Object.entries(helper.handlers)) { + wallet.registerEip1193(method, handler); + } + + return helper; +} diff --git a/src/ows/registerCredentialsProvider.ts b/src/ows/registerCredentialsProvider.ts new file mode 100644 index 0000000..ca76698 --- /dev/null +++ b/src/ows/registerCredentialsProvider.ts @@ -0,0 +1,64 @@ +import type { OWSSigner } from "@1shotapi/ows-signer-utils"; +import type { OWSWallet } from "@1shotapi/ows-wallet-utils"; +import { + CredentialsHelper, + type CredentialsHelperOptions, +} from "@1shotapi/ows-oid4"; +import { + type CredentialOfferApprovalRequest, + type CredentialPresentationApprovalRequest, + type ICredentialRepository, + type ICredentialStatusValidator, + type IHolderSigner, + type IIssuerTrustRegistry, + type IOid4vciClient, + type IOid4vpClient, + type IWalletAttestationProvider, + type IssuerMetadata, +} from "@1shotapi/ows-types"; + +export type RegisterCredentialsProviderOptions = { + repository: ICredentialRepository; + oid4vci: IOid4vciClient; + oid4vp: IOid4vpClient; + trust: IIssuerTrustRegistry; + status?: ICredentialStatusValidator; + holderSigner?: IHolderSigner | (() => Promise); + getProofNonce?: (metadata: IssuerMetadata) => string | Promise; + attestationProvider?: IWalletAttestationProvider; + ensureReady?: () => Promise; + requestCredentialOfferApproval?: ( + request: CredentialOfferApprovalRequest, + ) => Promise; + requestCredentialPresentationApproval?: ( + request: CredentialPresentationApprovalRequest, + ) => Promise; +}; + +/** + * Register wallet.credentials handlers via {@link CredentialsHelper} + * (call before `wallet.start()`). + */ +export function registerCredentialsProvider( + wallet: OWSWallet, + signer: OWSSigner, + options: RegisterCredentialsProviderOptions, +): CredentialsHelper { + const helperOptions: CredentialsHelperOptions = { + repository: options.repository, + oid4vci: options.oid4vci, + oid4vp: options.oid4vp, + trust: options.trust, + status: options.status, + holderSigner: options.holderSigner, + getProofNonce: options.getProofNonce, + attestationProvider: options.attestationProvider, + ensureReady: options.ensureReady, + requestCredentialOfferApproval: options.requestCredentialOfferApproval, + requestCredentialPresentationApproval: + options.requestCredentialPresentationApproval, + }; + const helper = new CredentialsHelper(wallet, signer, helperOptions); + helper.register(); + return helper; +} diff --git a/src/storage.ts b/src/storage.ts new file mode 100644 index 0000000..bdc836c --- /dev/null +++ b/src/storage.ts @@ -0,0 +1,69 @@ +import { + EVMAccountAddress, + SolanaAccountAddress, +} from "@1shotapi/ows-types"; + +const WALLET_CREATED_KEY = "ows-wallet-created"; +const CREDENTIAL_ID_KEY = "ows-credential-id"; +const BACKUP_KEY = "ows-wallet-backup"; +const EVM_ADDRESS_KEY = "ows-evm-address"; +const SOLANA_ADDRESS_KEY = "ows-solana-address"; + +export function isWalletCreated(): boolean { + return localStorage.getItem(WALLET_CREATED_KEY) === "true"; +} + +export function loadCredentialId(): string | undefined { + return localStorage.getItem(CREDENTIAL_ID_KEY) ?? undefined; +} + +export function saveWalletCreated(credentialId: string): void { + localStorage.setItem(WALLET_CREATED_KEY, "true"); + localStorage.setItem(CREDENTIAL_ID_KEY, credentialId); +} + +export function loadCachedEvmAddress(): EVMAccountAddress | undefined { + const value = localStorage.getItem(EVM_ADDRESS_KEY); + if (!value) { + return undefined; + } + return EVMAccountAddress(value as `0x${string}`); +} + +export function loadCachedSolanaAddress(): SolanaAccountAddress | undefined { + const value = localStorage.getItem(SOLANA_ADDRESS_KEY); + if (!value) { + return undefined; + } + return SolanaAccountAddress(value); +} + +export function saveCachedAddresses( + evm: EVMAccountAddress, + solana?: SolanaAccountAddress, +): void { + localStorage.setItem(EVM_ADDRESS_KEY, evm); + if (solana) { + localStorage.setItem(SOLANA_ADDRESS_KEY, solana); + } +} + +export function clearWalletStorage(): void { + localStorage.removeItem(WALLET_CREATED_KEY); + localStorage.removeItem(CREDENTIAL_ID_KEY); + localStorage.removeItem(EVM_ADDRESS_KEY); + localStorage.removeItem(SOLANA_ADDRESS_KEY); +} + +/** App-owned backup blob (`ows1:…`). */ +export function saveBackup(encryptedPrivateKey: string): void { + localStorage.setItem(BACKUP_KEY, encryptedPrivateKey); +} + +export function loadBackup(): string | undefined { + return localStorage.getItem(BACKUP_KEY) ?? undefined; +} + +export function hasBackup(): boolean { + return Boolean(loadBackup()); +} diff --git a/src/wallet/WalletProvider.tsx b/src/wallet/WalletProvider.tsx new file mode 100644 index 0000000..d35bdc6 --- /dev/null +++ b/src/wallet/WalletProvider.tsx @@ -0,0 +1,693 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, + type ReactNode, + type RefObject, +} from "react"; +import { OWSSigner } from "@1shotapi/ows-signer-utils"; +import { OWSWallet, RpcHelper } from "@1shotapi/ows-wallet-utils"; +import { + EVMAccountAddress, + EVMChainId, + OwsUserRejectedError, + SolanaAccountAddress, + type CredentialOfferApprovalRequest, + type CredentialPresentationApprovalRequest, +} from "@1shotapi/ows-types"; +import type { + PersonalSignApprovalRequest, + SignTypedDataApprovalRequest, +} from "@1shotapi/ows-signer-utils"; +import { DEMO_HOLDER_PRIVATE_JWK } from "../demo/demo-keys"; +import { InMemoryIssuerTrustRegistry } from "../demo/in-memory-trust-registry"; +import { LocalStorageCredentialRepository } from "../demo/local-storage-store"; +import { + DemoWalletAttestationProvider, + FetchUtils, + HttpOid4vciClient, + HttpOid4vpClient, + ParseUtils, +} from "@1shotapi/ows-oid4"; +import { DEMO_CHAINS } from "../ows/demoChains"; +import { registerAccountConnect } from "../ows/registerAccountConnect"; +import { registerApprovalSigning } from "../ows/registerApprovalSigning"; +import { registerCredentialsProvider } from "../ows/registerCredentialsProvider"; +import { + isWalletCreated, + loadBackup, + loadCachedEvmAddress, + loadCachedSolanaAddress, + loadCredentialId, + saveBackup, + saveCachedAddresses, + saveWalletCreated, +} from "../storage"; +import { + nextModalId, + type ActiveModal, + type WalletSetupChoice, +} from "./modalTypes"; + +const credentialRepository = new LocalStorageCredentialRepository(); +const issuerTrust = new InMemoryIssuerTrustRegistry(); +const fetchUtils = new FetchUtils(); +const parseUtils = new ParseUtils(); +const oid4vci = new HttpOid4vciClient(fetchUtils, parseUtils); +const oid4vp = new HttpOid4vpClient(fetchUtils); +const attestationProvider = new DemoWalletAttestationProvider({ + privateJwk: DEMO_HOLDER_PRIVATE_JWK, + issuer: "ows-demo-wallet", +}); + +const walletStorage = { + isWalletCreated, + loadCredentialId, + saveWalletCreated, + saveCachedAddresses, + loadCachedEvmAddress, +}; + +/** + * Proxy that forwards to a real {@link OWSSigner} once `awaitSigner` resolves. + * Register Postmate handlers with this so `wallet.start()` can run before the + * nested Signing Layer iframe finishes loading (Postmate parents give up after + * ~2.5s of handshake retries). + */ +function createDeferredSigner( + awaitSigner: () => Promise, +): OWSSigner { + let instance: OWSSigner | undefined; + let loadError: unknown; + void awaitSigner() + .then((signer) => { + instance = signer; + }) + .catch((error: unknown) => { + loadError = error; + console.error( + "[ows-example-general-wallet] deferred Signing Layer load failed", + error, + ); + }); + return new Proxy({} as OWSSigner, { + get(_target, property) { + // Avoid looking like a thenable if someone awaits the proxy. + if (property === "then") { + return undefined; + } + if (!instance) { + if (loadError !== undefined) { + throw loadError instanceof Error + ? loadError + : new Error( + `Signing Layer failed to load: ${String(loadError)}`, + ); + } + throw new Error( + "Signing Layer not ready — await ensureReady() before using the signer", + ); + } + const value = Reflect.get(instance, property, instance); + return typeof value === "function" + ? (value as (...args: unknown[]) => unknown).bind(instance) + : value; + }, + }); +} + +export type WalletContextValue = { + ready: boolean; + bootError: string | null; + embedded: boolean; + unlocked: boolean; + walletCreated: boolean; + evmAddress: EVMAccountAddress; + solanaAddress: SolanaAccountAddress; + chainId: EVMChainId; + chains: typeof DEMO_CHAINS; + credentialCount: number; + activeModal: ActiveModal | null; + signerContainerRef: RefObject; + getSigner: () => OWSSigner | null; + /** Resolves when the Signing Layer iframe has finished loading. */ + awaitSignerReady: () => Promise; + /** Awaits Signing Layer load, then unlocks / runs setup if needed. */ + ensureReady: () => Promise; + setUnlocked: (value: boolean) => void; + refreshAddresses: () => Promise; + refreshCredentialCount: () => Promise; + switchChain: (chainId: string) => Promise; + requestHide: () => Promise; + openCredentialList: () => Promise; + openCreateBackup: () => Promise; + openRestoreBackup: () => Promise; + loginWithPasskey: () => Promise; + createNewWalletFromUi: () => Promise; + persistBackup: (encryptedPrivateKey: string) => void; +}; + +const WalletContext = createContext(null); + +export function useWallet(): WalletContextValue { + const value = useContext(WalletContext); + if (!value) { + throw new Error("useWallet must be used within WalletProvider"); + } + return value; +} + +export function WalletProvider({ children }: { children: ReactNode }) { + const [ready, setReady] = useState(false); + const [bootError, setBootError] = useState(null); + const [unlocked, setUnlockedState] = useState(false); + const [walletCreated, setWalletCreated] = useState(() => isWalletCreated()); + const [evmAddress, setEvmAddress] = useState( + () => loadCachedEvmAddress() ?? EVMAccountAddress("0x0"), + ); + const [solanaAddress, setSolanaAddress] = useState( + () => loadCachedSolanaAddress() ?? SolanaAccountAddress("—"), + ); + const [chainId, setChainId] = useState(DEMO_CHAINS[0]!.chainId); + const [credentialCount, setCredentialCount] = useState(0); + const [activeModal, setActiveModal] = useState(null); + const [embedded] = useState(() => window.parent !== window); + + const signerContainerRef = useRef(null); + const walletRef = useRef(null); + const signerRef = useRef(null); + const rpcHelperRef = useRef(null); + const unlockedRef = useRef(false); + const unlockInFlightRef = useRef | undefined>(undefined); + const modalQueueRef = useRef([]); + + const advanceQueue = useCallback(() => { + const next = modalQueueRef.current[0] ?? null; + setActiveModal(next); + }, []); + + const removeModal = useCallback( + (id: string) => { + modalQueueRef.current = modalQueueRef.current.filter((m) => m.id !== id); + advanceQueue(); + }, + [advanceQueue], + ); + + const pushModal = useCallback( + ( + build: (handlers: { + id: string; + resolve: (value: T) => void; + reject: (error: unknown) => void; + }) => ActiveModal, + ): Promise => { + return new Promise((resolve, reject) => { + const id = nextModalId(); + let settled = false; + const finishResolve = (value: T) => { + if (settled) return; + settled = true; + removeModal(id); + resolve(value); + }; + const finishReject = (error: unknown) => { + if (settled) return; + settled = true; + removeModal(id); + reject(error); + }; + const modal = build({ + id, + resolve: finishResolve, + reject: finishReject, + }); + modalQueueRef.current.push(modal); + setActiveModal((current) => current ?? modal); + }); + }, + [removeModal], + ); + + const setUnlocked = useCallback((value: boolean) => { + unlockedRef.current = value; + setUnlockedState(value); + }, []); + + const refreshAddresses = useCallback(async () => { + const signer = signerRef.current; + if (!signer) return; + const evm = await signer.evm.getAccountAddress(); + const solana = await signer.solana.getAccountAddress(); + setEvmAddress(evm); + setSolanaAddress(solana); + saveCachedAddresses(evm, solana); + }, []); + + const refreshCredentialCount = useCallback(async () => { + const listed = await credentialRepository.list(); + setCredentialCount(listed.length); + }, []); + + const promptPasskeyName = useCallback((): Promise => { + return pushModal(({ id, resolve }) => ({ + id, + kind: "passkeyName", + resolve, + })); + }, [pushModal]); + + const requestWalletSetupChoice = + useCallback((): Promise => { + return pushModal(({ id, resolve }) => ({ + id, + kind: "walletSetup", + resolve, + })); + }, [pushModal]); + + const loginWithPasskey = useCallback(async () => { + const signer = signerRef.current; + if (!signer) throw new Error("Signer not ready"); + const result = await signer.getPublicKey({ discoverable: true }); + const credentialId = result.credentialId ?? signer.getCredentialId(); + if (!credentialId) { + throw new Error("Passkey login succeeded but credential id missing"); + } + saveWalletCreated(credentialId); + setWalletCreated(true); + await refreshAddresses(); + setUnlocked(true); + }, [refreshAddresses, setUnlocked]); + + const createNewWallet = useCallback( + async (accountName: string) => { + const signer = signerRef.current; + if (!signer) throw new Error("Signer not ready"); + await signer.createCredential(accountName, { + rpName: "Open Wallet", + userDisplayName: accountName, + }); + const credentialId = signer.getCredentialId(); + if (!credentialId) { + throw new Error("Passkey created but credential id missing"); + } + saveWalletCreated(credentialId); + setWalletCreated(true); + await refreshAddresses(); + setUnlocked(true); + }, + [refreshAddresses, setUnlocked], + ); + + const createNewWalletFromUi = useCallback(async () => { + const name = await promptPasskeyName(); + if (!name) { + throw new OwsUserRejectedError("User cancelled passkey creation"); + } + await createNewWallet(name); + }, [createNewWallet, promptPasskeyName]); + + const unlockWithStoredCredential = useCallback(async () => { + const signer = signerRef.current; + if (!signer) throw new Error("Signer not ready"); + const storedCredentialId = loadCredentialId(); + if (storedCredentialId) { + const result = await signer.getPublicKey({ + credentialId: storedCredentialId, + }); + const credentialId = result.credentialId ?? signer.getCredentialId(); + if (!credentialId) { + throw new Error("Passkey unlock succeeded but credential id missing"); + } + saveWalletCreated(credentialId); + setWalletCreated(true); + await refreshAddresses(); + setUnlocked(true); + return; + } + await loginWithPasskey(); + }, [loginWithPasskey, refreshAddresses, setUnlocked]); + + const runSetupFlow = useCallback(async () => { + const wallet = walletRef.current; + if (!wallet) throw new Error("Wallet not ready"); + const display = await wallet.requestDisplay({ width: 420, height: 480 }); + try { + const choice = await requestWalletSetupChoice(); + if (choice === "cancel") { + throw new OwsUserRejectedError("User cancelled wallet setup"); + } + if (choice === "login") { + await loginWithPasskey(); + return; + } + await createNewWalletFromUi(); + } finally { + await display.hide(); + } + }, [createNewWalletFromUi, loginWithPasskey, requestWalletSetupChoice]); + + const ensureReadyImpl = useCallback(async () => { + if (unlockedRef.current) { + return; + } + if (unlockInFlightRef.current) { + await unlockInFlightRef.current; + return; + } + + unlockInFlightRef.current = (async () => { + if (isWalletCreated()) { + await unlockWithStoredCredential(); + return; + } + await runSetupFlow(); + })(); + + try { + await unlockInFlightRef.current; + } finally { + unlockInFlightRef.current = undefined; + } + }, [runSetupFlow, unlockWithStoredCredential]); + + const ensureReadyRef = useRef(ensureReadyImpl); + ensureReadyRef.current = ensureReadyImpl; + + /** Resolves once `OWSSigner.create` finishes; set during boot. */ + const awaitSignerRef = useRef<(() => Promise) | null>(null); + + const awaitSignerReady = useCallback(async (): Promise => { + const awaitSigner = awaitSignerRef.current; + if (!awaitSigner) { + throw new Error( + "Signing Layer not started — wallet boot has not begun yet", + ); + } + return awaitSigner(); + }, []); + + const ensureReady = useCallback(async () => { + await awaitSignerReady(); + await ensureReadyRef.current(); + }, [awaitSignerReady]); + + const uiBridgeRef = useRef({ + pushModal, + }); + uiBridgeRef.current.pushModal = pushModal; + + const switchChain = useCallback(async (next: string) => { + const rpc = rpcHelperRef.current; + if (!rpc) return; + const previous = rpc.getChainId(); + try { + await rpc.switchChain(next); + } catch (error: unknown) { + setChainId(previous); + console.error("[ows-example-general-wallet] chain switch failed", error); + throw error; + } + }, []); + + const requestHide = useCallback(async () => { + await walletRef.current?.requestHide(); + }, []); + + const openCredentialList = useCallback(async () => { + const listed = await credentialRepository.list(); + setCredentialCount(listed.length); + await pushModal(({ id, resolve }) => ({ + id, + kind: "credentialList", + credentials: listed, + resolve, + })); + }, [pushModal]); + + const openCreateBackup = useCallback(async () => { + const wallet = walletRef.current; + if (!wallet) return; + const display = await wallet.requestDisplay({ width: 480, height: 420 }); + try { + await pushModal(({ id, resolve, reject }) => ({ + id, + kind: "createBackup", + resolve, + reject, + })); + } finally { + await display.hide(); + } + }, [pushModal]); + + const openRestoreBackup = useCallback(async () => { + const encrypted = loadBackup(); + if (!encrypted) { + window.alert("No backup found. Create a backup first."); + return; + } + const wallet = walletRef.current; + if (!wallet) return; + const display = await wallet.requestDisplay({ width: 480, height: 420 }); + try { + const restored = await pushModal(({ id, resolve, reject }) => ({ + id, + kind: "restoreBackup", + encryptedPrivateKey: encrypted, + resolve, + reject, + })); + if (restored) { + setUnlocked(true); + setWalletCreated(true); + await refreshAddresses(); + } + } finally { + await display.hide(); + } + }, [pushModal, refreshAddresses, setUnlocked]); + + const persistBackup = useCallback((encryptedPrivateKey: string) => { + saveBackup(encryptedPrivateKey); + }, []); + + const getSigner = useCallback(() => signerRef.current, []); + + useEffect(() => { + let cancelled = false; + + async function boot(): Promise { + // Wait a tick so SignerHost has committed the ref. + await Promise.resolve(); + const container = signerContainerRef.current; + if (!container) { + throw new Error("#signer-container not mounted"); + } + + // Kick off Signing Layer load without blocking the host Postmate handshake. + // Postmate parents only retry ~5 times (~2.5s after iframe load); awaiting + // the nested /signer/ iframe (especially over ngrok) exceeds that window. + const signerUrl = new URL("/signer/", window.location.origin).href; + const signerPromise = OWSSigner.create(container, signerUrl, { + hidden: true, + credentialId: loadCredentialId(), + }); + const awaitSigner = async (): Promise => { + const loaded = await signerPromise; + signerRef.current = loaded; + return loaded; + }; + awaitSignerRef.current = awaitSigner; + // Handlers close over this proxy; they must call ensureReady (awaits signer) + // before touching signing APIs. + const signer = createDeferredSigner(awaitSigner); + + const wallet = OWSWallet.prepare({ debug: true }); + walletRef.current = wallet; + + const ask = ( + build: (handlers: { + id: string; + resolve: (value: T) => void; + reject: (error: unknown) => void; + }) => ActiveModal, + ) => uiBridgeRef.current.pushModal(build); + + registerAccountConnect(wallet, signer, { + storage: walletStorage, + ensureReady, + requestConnectApproval: () => + ask(({ id, resolve }) => ({ + id, + kind: "connect", + resolve, + })), + }); + + registerApprovalSigning(wallet, signer, { + ensureReady, + requestPersonalSignApproval: (request: PersonalSignApprovalRequest) => + ask(({ id, resolve }) => ({ + id, + kind: "personalSign", + request, + resolve, + })), + requestSignTypedDataApproval: ( + request: SignTypedDataApprovalRequest, + ) => + ask(({ id, resolve }) => ({ + id, + kind: "typedData", + request, + resolve, + })), + }); + + registerCredentialsProvider(wallet, signer, { + repository: credentialRepository, + oid4vci, + oid4vp, + trust: issuerTrust, + attestationProvider, + ensureReady, + requestCredentialOfferApproval: ( + request: CredentialOfferApprovalRequest, + ) => + ask(({ id, resolve }) => ({ + id, + kind: "credentialOffer", + request, + resolve, + })), + requestCredentialPresentationApproval: ( + request: CredentialPresentationApprovalRequest, + ) => + ask(({ id, resolve }) => ({ + id, + kind: "credentialPresentation", + request, + resolve, + })), + }); + + const defaultChainId = DEMO_CHAINS[0]!.chainId; + const rpcHelper = new RpcHelper( + new Map(DEMO_CHAINS.map((chain) => [chain.chainId, chain.rpcUrl])), + wallet, + signer, + { defaultChainId }, + ); + rpcHelperRef.current = rpcHelper; + setChainId(rpcHelper.getChainId()); + rpcHelper.events.on("chainChanged", (next) => { + setChainId(next); + }); + + // Register Postmate.Model immediately — before nested signer iframe load. + void wallet.start().catch((error: unknown) => { + if (cancelled) return; + console.error( + "[ows-example-general-wallet] Postmate handshake failed", + error, + ); + setBootError(error instanceof Error ? error.message : String(error)); + }); + + // Finish Signing Layer init in the background; UI can paint meanwhile. + void awaitSigner().catch((error: unknown) => { + if (cancelled) return; + console.error( + "[ows-example-general-wallet] Signing Layer failed to load", + error, + ); + setBootError(error instanceof Error ? error.message : String(error)); + }); + + // Paint UI without awaiting host handshake — standalone /wallet/ has no parent. + const listed = await credentialRepository.list(); + if (cancelled) return; + setCredentialCount(listed.length); + setReady(true); + console.info("[ows-example-general-wallet] ready", { + chainId: rpcHelper.getChainId(), + }); + } + + void boot().catch((error: unknown) => { + console.error("[ows-example-general-wallet] failed to start", error); + setBootError(error instanceof Error ? error.message : String(error)); + }); + + return () => { + cancelled = true; + }; + }, []); + + const value = useMemo( + () => ({ + ready, + bootError, + embedded, + unlocked, + walletCreated, + evmAddress, + solanaAddress, + chainId, + chains: DEMO_CHAINS, + credentialCount, + activeModal, + signerContainerRef, + getSigner, + awaitSignerReady, + ensureReady, + setUnlocked, + refreshAddresses, + refreshCredentialCount, + switchChain, + requestHide, + openCredentialList, + openCreateBackup, + openRestoreBackup, + loginWithPasskey, + createNewWalletFromUi, + persistBackup, + }), + [ + ready, + bootError, + embedded, + unlocked, + walletCreated, + evmAddress, + solanaAddress, + chainId, + credentialCount, + activeModal, + getSigner, + awaitSignerReady, + ensureReady, + setUnlocked, + refreshAddresses, + refreshCredentialCount, + switchChain, + requestHide, + openCredentialList, + openCreateBackup, + openRestoreBackup, + loginWithPasskey, + createNewWalletFromUi, + persistBackup, + ], + ); + + return ( + {children} + ); +} diff --git a/src/wallet/modalTypes.ts b/src/wallet/modalTypes.ts new file mode 100644 index 0000000..7df2be4 --- /dev/null +++ b/src/wallet/modalTypes.ts @@ -0,0 +1,83 @@ +import type { + PersonalSignApprovalRequest, + SignTypedDataApprovalRequest, +} from "@1shotapi/ows-signer-utils"; +import type { + CredentialOfferApprovalRequest, + CredentialPresentationApprovalRequest, + CredentialSummary, + RecoveryDataCreatedData, +} from "@1shotapi/ows-types"; + +export type WalletSetupChoice = "login" | "create" | "cancel"; + +export type ModalRequest = + | { + id: string; + kind: "walletSetup"; + resolve: (choice: WalletSetupChoice) => void; + } + | { + id: string; + kind: "passkeyName"; + resolve: (name: string | null) => void; + } + | { + id: string; + kind: "connect"; + resolve: (approved: boolean) => void; + } + | { + id: string; + kind: "personalSign"; + request: PersonalSignApprovalRequest; + resolve: (approved: boolean) => void; + } + | { + id: string; + kind: "typedData"; + request: SignTypedDataApprovalRequest; + resolve: (approved: boolean) => void; + } + | { + id: string; + kind: "credentialOffer"; + request: CredentialOfferApprovalRequest; + resolve: (approved: boolean) => void; + } + | { + id: string; + kind: "credentialPresentation"; + request: CredentialPresentationApprovalRequest; + resolve: (approved: boolean) => void; + } + | { + id: string; + kind: "credentialList"; + credentials: CredentialSummary[]; + resolve: () => void; + } + | { + id: string; + kind: "createBackup"; + resolve: () => void; + reject: (error: unknown) => void; + } + | { + id: string; + kind: "restoreBackup"; + encryptedPrivateKey: string; + resolve: (restored: boolean) => void; + reject: (error: unknown) => void; + }; + +export type ActiveModal = ModalRequest; + +let modalId = 0; + +export function nextModalId(): string { + modalId += 1; + return `modal-${modalId}`; +} + +export type CreateBackupResult = RecoveryDataCreatedData; diff --git a/vendor/ows-signer/package.json b/vendor/ows-signer/package.json deleted file mode 100644 index b811e87..0000000 --- a/vendor/ows-signer/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@1shotapi/ows-signer", - "version": "0.1.0", - "description": "OWS Signing Layer — plain JavaScript WebAuthn PRF key custody with curve-based signing (on-chain via EIP-8244)", - "type": "module", - "files": [ - "src", - "html", - "README.md" - ], - "exports": { - "./html": "./html/index.html", - "./src/*": "./src/*" - }, - "scripts": { - "test": "node --test test/*.test.js" - }, - "keywords": [ - "ows", - "open-wallet-standard", - "webauthn", - "prf", - "passkey", - "signer", - "custody", - "secp256k1" - ], - "repository": { - "type": "git", - "url": "https://github.com/1Shot-API/open-wallet.git", - "directory": "packages/ows-signer" - }, - "license": "MIT", - "publishConfig": { - "access": "public" - } -} diff --git a/vendor/ows-signer/src/constants.js b/vendor/ows-signer/src/constants.js deleted file mode 100644 index d5bc1d7..0000000 --- a/vendor/ows-signer/src/constants.js +++ /dev/null @@ -1,30 +0,0 @@ -/** @typedef {'secp256k1-ecdsa' | 'secp256k1-ecdsa-recoverable' | 'secp256k1-bip340' | 'ed25519'} SignScheme */ - -export const API_VERSION = 1; -export const SIGNER_VERSION = "0.1.0"; - -export const PRF_LABEL_SECP256K1 = new TextEncoder().encode("ows-v1/secp256k1"); -export const PRF_LABEL_ED25519 = new TextEncoder().encode("ows-v1/ed25519"); -/** HKDF info for AES-256-GCM — IKM is the secp256k1 scalar (`signDigest` material). */ -export const PRF_LABEL_AES256 = new TextEncoder().encode("ows-v1/aes256-gcm"); - -/** @type {SignScheme[]} */ -export const SIGN_SCHEMES = [ - "secp256k1-ecdsa", - "secp256k1-ecdsa-recoverable", - "secp256k1-bip340", - "ed25519", -]; - -export const METHODS = [ - "getVersion", - "createCredential", - "signDigest", - "revealPrivateKey", - "createRecoveryData", - "recoverKey", - "getPublicKey", - "clearRecoverySession", - "encryptAES256", - "decryptAES256", -]; diff --git a/vendor/ows-signer/src/crypto/aes256.js b/vendor/ows-signer/src/crypto/aes256.js deleted file mode 100644 index 5df1c48..0000000 --- a/vendor/ows-signer/src/crypto/aes256.js +++ /dev/null @@ -1,115 +0,0 @@ -import { PRF_LABEL_AES256 } from "../constants.js"; -import { parse0xHex, to0xHex } from "../hex.js"; -import { hkdfExpand } from "./prf.js"; - -/** Envelope prefix — distinct from recovery `ows1:` (PBKDF2). */ -export const AES256_ENVELOPE_PREFIX = "ows-aes1:"; -const AES256_VERSION = 1; -const IV_LENGTH = 12; - -/** - * Derive an AES-256-GCM CryptoKey from the wallet secp256k1 scalar - * (same material `signDigest` uses), via HKDF domain separation. - * - * @param {Uint8Array} secp256k1PrivateKey - * @returns {Promise} - */ -export async function deriveAes256KeyFromSecp256k1(secp256k1PrivateKey) { - const raw = await hkdfExpand( - secp256k1PrivateKey.buffer.slice( - secp256k1PrivateKey.byteOffset, - secp256k1PrivateKey.byteOffset + secp256k1PrivateKey.byteLength, - ), - PRF_LABEL_AES256, - 32, - ); - try { - return await crypto.subtle.importKey( - "raw", - raw, - { name: "AES-GCM", length: 256 }, - false, - ["encrypt", "decrypt"], - ); - } finally { - raw.fill(0); - } -} - -/** - * Encrypt a UTF-8 plaintext. Envelope: `ows-aes1:0x` ‖ version ‖ IV ‖ ciphertext+tag. - * - * @param {string} plaintext - * @param {CryptoKey} aesKey - * @returns {Promise} - */ -export async function encryptAes256String(plaintext, aesKey) { - const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)); - const encoded = new TextEncoder().encode(plaintext); - const ciphertext = new Uint8Array( - await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, encoded), - ); - const payload = new Uint8Array(1 + IV_LENGTH + ciphertext.length); - payload[0] = AES256_VERSION; - payload.set(iv, 1); - payload.set(ciphertext, 1 + IV_LENGTH); - return `${AES256_ENVELOPE_PREFIX}${to0xHex(payload)}`; -} - -/** - * Decrypt an `ows-aes1:` envelope to a UTF-8 string. - * - * @param {string} envelope - * @param {CryptoKey} aesKey - * @returns {Promise} - */ -export async function decryptAes256String(envelope, aesKey) { - if (!envelope.startsWith(AES256_ENVELOPE_PREFIX)) { - throw new Error("invalidAes256Envelope"); - } - const bytes = parse0xHex(envelope.slice(AES256_ENVELOPE_PREFIX.length)); - if (bytes.length < 1 + IV_LENGTH + 16) { - throw new Error("invalidAes256Envelope"); - } - if (bytes[0] !== AES256_VERSION) { - throw new Error("unsupportedAes256Version"); - } - const iv = bytes.slice(1, 1 + IV_LENGTH); - const ciphertext = bytes.slice(1 + IV_LENGTH); - try { - const plain = new Uint8Array( - await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, ciphertext), - ); - return new TextDecoder().decode(plain); - } catch { - throw new Error("decryptionFailed"); - } -} - -/** - * @param {string[]} plaintexts - * @param {Uint8Array} secp256k1PrivateKey - * @returns {Promise} - */ -export async function encryptAes256Batch(plaintexts, secp256k1PrivateKey) { - const aesKey = await deriveAes256KeyFromSecp256k1(secp256k1PrivateKey); - const out = []; - for (const plaintext of plaintexts) { - out.push(await encryptAes256String(plaintext, aesKey)); - } - return out; -} - -/** - * @param {string[]} envelopes - * @param {Uint8Array} secp256k1PrivateKey - * @returns {Promise} - */ -export async function decryptAes256Batch(envelopes, secp256k1PrivateKey) { - const aesKey = await deriveAes256KeyFromSecp256k1(secp256k1PrivateKey); - const out = []; - for (const envelope of envelopes) { - out.push(await decryptAes256String(envelope, aesKey)); - } - return out; -} diff --git a/vendor/ows-signer/src/crypto/prf.js b/vendor/ows-signer/src/crypto/prf.js deleted file mode 100644 index b3b477f..0000000 --- a/vendor/ows-signer/src/crypto/prf.js +++ /dev/null @@ -1,119 +0,0 @@ -import { PRF_LABEL_ED25519, PRF_LABEL_SECP256K1 } from "../constants.js"; -import { getPublicKeyAsync as edGetPublicKeyAsync } from "./vendor/noble-ed25519.js"; -import { getPublicKey as secpGetPublicKey } from "./vendor/noble-secp256k1.js"; - -const SECP256K1_N = BigInt( - "0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", -); - -/** - * @param {unknown} prfOutput - * @returns {ArrayBuffer | null} - */ -export function normalizePrfOutputToArrayBuffer(prfOutput) { - if (prfOutput instanceof ArrayBuffer) return prfOutput; - if (prfOutput instanceof Uint8Array) { - return prfOutput.buffer.slice( - prfOutput.byteOffset, - prfOutput.byteOffset + prfOutput.byteLength, - ); - } - if (ArrayBuffer.isView(prfOutput)) { - const view = /** @type {ArrayBufferView} */ (prfOutput); - return view.buffer.slice( - view.byteOffset, - view.byteOffset + view.byteLength, - ); - } - if (Array.isArray(prfOutput)) { - return new Uint8Array(prfOutput).buffer; - } - return null; -} - -/** - * @param {ArrayBuffer} prfBuffer - * @param {Uint8Array} infoLabel - * @param {number} length - * @returns {Promise} - */ -export async function hkdfExpand(prfBuffer, infoLabel, length) { - const ikm = new Uint8Array(prfBuffer); - const baseKey = await crypto.subtle.importKey("raw", ikm, "HKDF", false, [ - "deriveBits", - ]); - const salt = new Uint8Array(32); - const bits = await crypto.subtle.deriveBits( - { - name: "HKDF", - hash: "SHA-256", - salt, - info: infoLabel, - }, - baseKey, - length * 8, - ); - return new Uint8Array(bits); -} - -/** - * @param {ArrayBuffer} prfBuffer - * @param {Uint8Array} infoLabel - * @returns {Promise} - */ -export async function deriveScalarFromPrf(prfBuffer, infoLabel) { - for (let counter = 0; counter < 255; counter++) { - const info = new Uint8Array(infoLabel.length + 1); - info.set(infoLabel); - info[infoLabel.length] = counter; - const candidate = await hkdfExpand(prfBuffer, info, 32); - const scalar = bytesToBigInt(candidate); - if (scalar > 0n && scalar < SECP256K1_N) { - return candidate; - } - } - throw new Error("Failed to derive valid secp256k1 scalar from PRF"); -} - -/** - * @param {ArrayBuffer} prfBuffer - * @returns {Promise} - */ -export async function deriveEd25519SeedFromPrf(prfBuffer) { - return hkdfExpand(prfBuffer, PRF_LABEL_ED25519, 32); -} - -/** - * @param {PublicKeyCredential} credential - * @returns {Promise<{ secp256k1PrivateKey: Uint8Array, secp256k1PublicKey: Uint8Array, ed25519PublicKey: Uint8Array }>} - */ -export async function deriveKeysFromCredential(credential) { - const rawPrf = /** @type {{ prf?: { results?: { first?: unknown } } }} */ ( - credential.getClientExtensionResults() - ).prf?.results?.first; - const prfBuffer = normalizePrfOutputToArrayBuffer(rawPrf); - if (!prfBuffer) { - throw new Error("Passkey does not support PRF or PRF output missing"); - } - - const secp256k1PrivateKey = await deriveScalarFromPrf( - prfBuffer, - PRF_LABEL_SECP256K1, - ); - const ed25519Seed = await deriveEd25519SeedFromPrf(prfBuffer); - // Uncompressed (0x04 ‖ X ‖ Y) — required by viem `publicKeyToAddress`. - const secp256k1PublicKey = secpGetPublicKey(secp256k1PrivateKey, false); - const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed); - - return { secp256k1PrivateKey, secp256k1PublicKey, ed25519PublicKey }; -} - -/** - * @param {Uint8Array} bytes - * @returns {bigint} - */ -function bytesToBigInt(bytes) { - let result = 0n; - for (const b of bytes) result = (result << 8n) + BigInt(b); - return result; -} diff --git a/vendor/ows-signer/src/crypto/recovery.js b/vendor/ows-signer/src/crypto/recovery.js deleted file mode 100644 index d68f5c2..0000000 --- a/vendor/ows-signer/src/crypto/recovery.js +++ /dev/null @@ -1,81 +0,0 @@ -import { parse0xHex, to0xHex } from "../hex.js"; - -const RECOVERY_VERSION = 1; -const PBKDF2_ITERATIONS = 250_000; - -/** - * @param {string} passphrase - * @param {Uint8Array} salt - * @returns {Promise} - */ -async function deriveAesKey(passphrase, salt) { - const enc = new TextEncoder(); - const keyMaterial = await crypto.subtle.importKey( - "raw", - enc.encode(passphrase), - "PBKDF2", - false, - ["deriveKey"], - ); - return crypto.subtle.deriveKey( - { - name: "PBKDF2", - salt, - iterations: PBKDF2_ITERATIONS, - hash: "SHA-256", - }, - keyMaterial, - { name: "AES-GCM", length: 256 }, - false, - ["encrypt", "decrypt"], - ); -} - -/** - * @param {Uint8Array} privateKey - * @param {string} passphrase - * @returns {Promise} - */ -export async function encryptPrivateKey(privateKey, passphrase) { - const salt = crypto.getRandomValues(new Uint8Array(16)); - const iv = crypto.getRandomValues(new Uint8Array(12)); - const aesKey = await deriveAesKey(passphrase, salt); - const ciphertext = new Uint8Array( - await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, privateKey), - ); - const payload = new Uint8Array( - 1 + salt.length + iv.length + ciphertext.length, - ); - payload[0] = RECOVERY_VERSION; - payload.set(salt, 1); - payload.set(iv, 1 + salt.length); - payload.set(ciphertext, 1 + salt.length + iv.length); - return `ows1:${to0xHex(payload)}`; -} - -/** - * @param {string} envelope - * @returns {Promise} - */ -export async function decryptPrivateKey(envelope, passphrase) { - if (!envelope.startsWith("ows1:")) { - throw new Error("invalidRecoveryEnvelope"); - } - const bytes = parse0xHex(envelope.slice(5)); - if (bytes[0] !== RECOVERY_VERSION) { - throw new Error("unsupportedRecoveryVersion"); - } - const salt = bytes.slice(1, 17); - const iv = bytes.slice(17, 29); - const ciphertext = bytes.slice(29); - const aesKey = await deriveAesKey(passphrase, salt); - try { - const plain = new Uint8Array( - await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, ciphertext), - ); - if (plain.length !== 32) throw new Error("invalidDecryptedKey"); - return plain; - } catch { - throw new Error("decryptionFailed"); - } -} diff --git a/vendor/ows-signer/src/crypto/sign.js b/vendor/ows-signer/src/crypto/sign.js deleted file mode 100644 index 92b6f2f..0000000 --- a/vendor/ows-signer/src/crypto/sign.js +++ /dev/null @@ -1,59 +0,0 @@ -import { to0xHex } from "../hex.js"; -import { signAsync as edSignAsync } from "./vendor/noble-ed25519.js"; -import { signAsync as secpSignAsync } from "./vendor/noble-secp256k1.js"; - -/** - * @param {import('../constants.js').SignScheme} scheme - * @param {Uint8Array} digest - * @param {Uint8Array} secp256k1PrivateKey - * @param {Uint8Array} ed25519Seed - * @returns {Promise<`0x${string}`>} - */ -export async function signWithScheme( - scheme, - digest, - secp256k1PrivateKey, - ed25519Seed, -) { - switch (scheme) { - case "secp256k1-ecdsa": { - const sig = await secpSignAsync(digest, secp256k1PrivateKey, { - lowS: true, - }); - return to0xHex(sig.toCompactRawBytes()); - } - case "secp256k1-ecdsa-recoverable": { - const sig = await secpSignAsync(digest, secp256k1PrivateKey, { - lowS: true, - }); - const compact = sig.toCompactRawBytes(); - const recovery = sig.recovery ?? 0; - const out = new Uint8Array(65); - out.set(compact, 0); - out[64] = recovery; - return to0xHex(out); - } - case "secp256k1-bip340": - throw new Error("schemeNotImplemented"); - case "ed25519": { - const sig = await edSignAsync(digest, ed25519Seed); - return to0xHex(sig); - } - default: - throw new Error("unknownScheme"); - } -} - -/** - * @param {import('../constants.js').SignScheme} scheme - * @param {Uint8Array} payload - */ -export function validateSignPayload(scheme, payload) { - if (scheme === "ed25519") { - if (payload.length === 0) throw new Error("invalidPayload"); - return; - } - if (payload.length !== 32) { - throw new Error("digestMustBe32Bytes"); - } -} diff --git a/vendor/ows-signer/src/crypto/vendor/noble-ed25519.js b/vendor/ows-signer/src/crypto/vendor/noble-ed25519.js deleted file mode 100644 index ab45032..0000000 --- a/vendor/ows-signer/src/crypto/vendor/noble-ed25519.js +++ /dev/null @@ -1,385 +0,0 @@ -/*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ -const P = 2n ** 255n - 19n; // ed25519 is twisted edwards curve -const N = 2n ** 252n + 27742317777372353535851937790883648493n; // curve's (group) order -const Gx = 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an; // base point x -const Gy = 0x6666666666666666666666666666666666666666666666666666666666666658n; // base point y -const CURVE = { - a: -1n, // where a=-1, d = -(121665/121666) == -(121665 * inv(121666)) mod P - d: 37095705934669439343138083508754565189542113879843219016388785533085940283555n, - p: P, n: N, h: 8, Gx, Gy // field prime, curve (group) order, cofactor -}; -const err = (m = '') => { throw new Error(m); }; // error helper, messes-up stack trace -const str = (s) => typeof s === 'string'; // is string -const isu8 = (a) => (a instanceof Uint8Array || - (a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')); -const au8 = (a, l) => // is Uint8Array (of specific length) - !isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ? - err('Uint8Array of valid length expected') : a; -const u8n = (data) => new Uint8Array(data); // creates Uint8Array -const toU8 = (a, len) => au8(str(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a -const mod = (a, b = P) => { let r = a % b; return r >= 0n ? r : b + r; }; // mod division -const isPoint = (p) => (p instanceof Point ? p : err('Point expected')); // is xyzt point -class Point { - constructor(ex, ey, ez, et) { - this.ex = ex; - this.ey = ey; - this.ez = ez; - this.et = et; - } - static fromAffine(p) { return new Point(p.x, p.y, 1n, mod(p.x * p.y)); } - static fromHex(hex, zip215 = false) { - const { d } = CURVE; - hex = toU8(hex, 32); - const normed = hex.slice(); // copy the array to not mess it up - const lastByte = hex[31]; - normed[31] = lastByte & ~0x80; // adjust first LE byte = last BE byte - const y = b2n_LE(normed); // decode as little-endian, convert to num - if (zip215 && !(0n <= y && y < 2n ** 256n)) - err('bad y coord 1'); // zip215=true [1..2^256-1] - if (!zip215 && !(0n <= y && y < P)) - err('bad y coord 2'); // zip215=false [1..P-1] - const y2 = mod(y * y); // y² - const u = mod(y2 - 1n); // u=y²-1 - const v = mod(d * y2 + 1n); // v=dy²+1 - let { isValid, value: x } = uvRatio(u, v); // (uv³)(uv⁷)^(p-5)/8; square root - if (!isValid) - err('bad y coordinate 3'); // not square root: bad point - const isXOdd = (x & 1n) === 1n; // adjust sign of x coordinate - const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit - if (!zip215 && x === 0n && isLastByteOdd) - err('bad y coord 3'); // x=0 and x_0 = 1 - if (isLastByteOdd !== isXOdd) - x = mod(-x); - return new Point(x, y, 1n, mod(x * y)); // Z=1, T=xy - } - get x() { return this.toAffine().x; } // .x, .y will call expensive toAffine. - get y() { return this.toAffine().y; } // Should be used with care. - equals(other) { - const { ex: X1, ey: Y1, ez: Z1 } = this; - const { ex: X2, ey: Y2, ez: Z2 } = isPoint(other); // isPoint() checks class equality - const X1Z2 = mod(X1 * Z2), X2Z1 = mod(X2 * Z1); - const Y1Z2 = mod(Y1 * Z2), Y2Z1 = mod(Y2 * Z1); - return X1Z2 === X2Z1 && Y1Z2 === Y2Z1; - } - is0() { return this.equals(I); } - negate() { - return new Point(mod(-this.ex), this.ey, this.ez, mod(-this.et)); - } - double() { - const { ex: X1, ey: Y1, ez: Z1 } = this; // Cost: 4M + 4S + 1*a + 6add + 1*2 - const { a } = CURVE; // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd - const A = mod(X1 * X1); - const B = mod(Y1 * Y1); - const C = mod(2n * mod(Z1 * Z1)); - const D = mod(a * A); - const x1y1 = X1 + Y1; - const E = mod(mod(x1y1 * x1y1) - A - B); - const G = D + B; - const F = G - C; - const H = D - B; - const X3 = mod(E * F); - const Y3 = mod(G * H); - const T3 = mod(E * H); - const Z3 = mod(F * G); - return new Point(X3, Y3, Z3, T3); - } - add(other) { - const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; // Cost: 8M + 1*k + 8add + 1*2. - const { ex: X2, ey: Y2, ez: Z2, et: T2 } = isPoint(other); // doesn't check if other on-curve - const { a, d } = CURVE; // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3 - const A = mod(X1 * X2); - const B = mod(Y1 * Y2); - const C = mod(T1 * d * T2); - const D = mod(Z1 * Z2); - const E = mod((X1 + Y1) * (X2 + Y2) - A - B); - const F = mod(D - C); - const G = mod(D + C); - const H = mod(B - a * A); - const X3 = mod(E * F); - const Y3 = mod(G * H); - const T3 = mod(E * H); - const Z3 = mod(F * G); - return new Point(X3, Y3, Z3, T3); - } - mul(n, safe = true) { - if (n === 0n) - return safe === true ? err('cannot multiply by 0') : I; - if (!(typeof n === 'bigint' && 0n < n && n < N)) - err('invalid scalar, must be < L'); - if (!safe && this.is0() || n === 1n) - return this; // safe=true bans 0. safe=false allows 0. - if (this.equals(G)) - return wNAF(n).p; // use wNAF precomputes for base points - let p = I, f = G; // init result point & fake point - for (let d = this; n > 0n; d = d.double(), n >>= 1n) { // double-and-add ladder - if (n & 1n) - p = p.add(d); // if bit is present, add to point - else if (safe) - f = f.add(d); // if not, add to fake for timing safety - } - return p; - } - multiply(scalar) { return this.mul(scalar); } // Aliases for compatibilty - clearCofactor() { return this.mul(BigInt(CURVE.h), false); } // multiply by cofactor - isSmallOrder() { return this.clearCofactor().is0(); } // check if P is small order - isTorsionFree() { - let p = this.mul(N / 2n, false).double(); // ensures the point is not "bad". - if (N % 2n) - p = p.add(this); // P^(N+1) // P*N == (P*(N/2))*2+P - return p.is0(); - } - toAffine() { - const { ex: x, ey: y, ez: z } = this; // (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy) - if (this.equals(I)) - return { x: 0n, y: 1n }; // fast-path for zero point - const iz = invert(z); // z^-1: invert z - if (mod(z * iz) !== 1n) - err('invalid inverse'); // (z * z^-1) must be 1, otherwise bad math - return { x: mod(x * iz), y: mod(y * iz) }; // x = x*z^-1; y = y*z^-1 - } - toRawBytes() { - const { x, y } = this.toAffine(); // convert to affine 2d point - const b = n2b_32LE(y); // encode number to 32 bytes - b[31] |= x & 1n ? 0x80 : 0; // store sign in first LE byte - return b; - } - toHex() { return b2h(this.toRawBytes()); } // encode to hex string -} -Point.BASE = new Point(Gx, Gy, 1n, mod(Gx * Gy)); // Generator / Base point -Point.ZERO = new Point(0n, 1n, 1n, 0n); // Identity / Zero point -const { BASE: G, ZERO: I } = Point; // Generator, identity points -const padh = (num, pad) => num.toString(16).padStart(pad, '0'); -const b2h = (b) => Array.from(b).map(e => padh(e, 2)).join(''); // bytes to hex -const h2b = (hex) => { - const l = hex.length; // error if not string, - if (!str(hex) || l % 2) - err('hex invalid 1'); // or has odd length like 3, 5. - const arr = u8n(l / 2); // create result array - for (let i = 0; i < arr.length; i++) { - const j = i * 2; - const h = hex.slice(j, j + 2); // hexByte. slice is faster than substr - const b = Number.parseInt(h, 16); // byte, created from string part - if (Number.isNaN(b) || b < 0) - err('hex invalid 2'); // byte must be valid 0 <= byte < 256 - arr[i] = b; - } - return arr; -}; -const n2b_32LE = (num) => h2b(padh(num, 32 * 2)).reverse(); // number to bytes LE -const b2n_LE = (b) => BigInt('0x' + b2h(u8n(au8(b)).reverse())); // bytes LE to num -const concatB = (...arrs) => { - const r = u8n(arrs.reduce((sum, a) => sum + au8(a).length, 0)); // create u8a of summed length - let pad = 0; // walk through each array, - arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type - return r; -}; -const invert = (num, md = P) => { - if (num === 0n || md <= 0n) - err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now - let a = mod(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n; - while (a !== 0n) { // uses euclidean gcd algorithm - const q = b / a, r = b % a; // not constant-time - const m = x - u * q, n = y - v * q; - b = a, a = r, x = u, y = v, u = m, v = n; - } - return b === 1n ? mod(x, md) : err('no inverse'); // b is gcd at this point -}; -const pow2 = (x, power) => { - let r = x; - while (power-- > 0n) { - r *= r; - r %= P; - } - return r; -}; -const pow_2_252_3 = (x) => { - const x2 = (x * x) % P; // x^2, bits 1 - const b2 = (x2 * x) % P; // x^3, bits 11 - const b4 = (pow2(b2, 2n) * b2) % P; // x^(2^4-1), bits 1111 - const b5 = (pow2(b4, 1n) * x) % P; // x^(2^5-1), bits 11111 - const b10 = (pow2(b5, 5n) * b5) % P; // x^(2^10) - const b20 = (pow2(b10, 10n) * b10) % P; // x^(2^20) - const b40 = (pow2(b20, 20n) * b20) % P; // x^(2^40) - const b80 = (pow2(b40, 40n) * b40) % P; // x^(2^80) - const b160 = (pow2(b80, 80n) * b80) % P; // x^(2^160) - const b240 = (pow2(b160, 80n) * b80) % P; // x^(2^240) - const b250 = (pow2(b240, 10n) * b10) % P; // x^(2^250) - const pow_p_5_8 = (pow2(b250, 2n) * x) % P; // < To pow to (p+3)/8, multiply it by x. - return { pow_p_5_8, b2 }; -}; -const RM1 = 19681161376707505956807079304988542015446066515923890162744021073123829784752n; // √-1 -const uvRatio = (u, v) => { - const v3 = mod(v * v * v); // v³ - const v7 = mod(v3 * v3 * v); // v⁷ - const pow = pow_2_252_3(u * v7).pow_p_5_8; // (uv⁷)^(p-5)/8 - let x = mod(u * v3 * pow); // (uv³)(uv⁷)^(p-5)/8 - const vx2 = mod(v * x * x); // vx² - const root1 = x; // First root candidate - const root2 = mod(x * RM1); // Second root candidate; RM1 is √-1 - const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root - const useRoot2 = vx2 === mod(-u); // If vx² = -u, set x <-- x * 2^((p-1)/4) - const noRoot = vx2 === mod(-u * RM1); // There is no valid root, vx² = -u√-1 - if (useRoot1) - x = root1; - if (useRoot2 || noRoot) - x = root2; // We return root2 anyway, for const-time - if ((mod(x) & 1n) === 1n) - x = mod(-x); // edIsNegative - return { isValid: useRoot1 || useRoot2, value: x }; -}; -const modL_LE = (hash) => mod(b2n_LE(hash), N); // modulo L; but little-endian -let _shaS; -const sha512a = (...m) => etc.sha512Async(...m); // Async SHA512 -const sha512s = (...m) => // Sync SHA512, not set by default - typeof _shaS === 'function' ? _shaS(...m) : err('etc.sha512Sync not set'); -const hash2extK = (hashed) => { - const head = hashed.slice(0, 32); // slice creates a copy, unlike subarray - head[0] &= 248; // Clamp bits: 0b1111_1000, - head[31] &= 127; // 0b0111_1111, - head[31] |= 64; // 0b0100_0000 - const prefix = hashed.slice(32, 64); // private key "prefix" - const scalar = modL_LE(head); // modular division over curve order - const point = G.mul(scalar); // public key point - const pointBytes = point.toRawBytes(); // point serialized to Uint8Array - return { head, prefix, scalar, point, pointBytes }; -}; -// RFC8032 5.1.5; getPublicKey async, sync. Hash priv key and extract point. -const getExtendedPublicKeyAsync = (priv) => sha512a(toU8(priv, 32)).then(hash2extK); -const getExtendedPublicKey = (priv) => hash2extK(sha512s(toU8(priv, 32))); -const getPublicKeyAsync = (priv) => getExtendedPublicKeyAsync(priv).then(p => p.pointBytes); -const getPublicKey = (priv) => getExtendedPublicKey(priv).pointBytes; -function hashFinish(asynchronous, res) { - if (asynchronous) - return sha512a(res.hashable).then(res.finish); - return res.finish(sha512s(res.hashable)); -} -const _sign = (e, rBytes, msg) => { - const { pointBytes: P, scalar: s } = e; - const r = modL_LE(rBytes); // r was created outside, reduce it modulo L - const R = G.mul(r).toRawBytes(); // R = [r]B - const hashable = concatB(R, P, msg); // dom2(F, C) || R || A || PH(M) - const finish = (hashed) => { - const S = mod(r + modL_LE(hashed) * s, N); // S = (r + k * s) mod L; 0 <= s < l - return au8(concatB(R, n2b_32LE(S)), 64); // 64-byte sig: 32b R.x + 32b LE(S) - }; - return { hashable, finish }; -}; -const signAsync = async (msg, privKey) => { - const m = toU8(msg); // RFC8032 5.1.6: sign msg with key async - const e = await getExtendedPublicKeyAsync(privKey); // pub,prfx - const rBytes = await sha512a(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M)) - return hashFinish(true, _sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature -}; -const sign = (msg, privKey) => { - const m = toU8(msg); // RFC8032 5.1.6: sign msg with key sync - const e = getExtendedPublicKey(privKey); // pub,prfx - const rBytes = sha512s(e.prefix, m); // r = SHA512(dom2(F, C) || prefix || PH(M)) - return hashFinish(false, _sign(e, rBytes, m)); // gen R, k, S, then 64-byte signature -}; -const dvo = { zip215: true }; -const _verify = (sig, msg, pub, opts = dvo) => { - msg = toU8(msg); // Message hex str/Bytes - sig = toU8(sig, 64); // Signature hex str/Bytes, must be 64 bytes - const { zip215 } = opts; // switch between zip215 and rfc8032 verif - let A, R, s, SB, hashable = new Uint8Array(); - try { - A = Point.fromHex(pub, zip215); // public key A decoded - R = Point.fromHex(sig.slice(0, 32), zip215); // 0 <= R < 2^256: ZIP215 R can be >= P - s = b2n_LE(sig.slice(32, 64)); // Decode second half as an integer S - SB = G.mul(s, false); // in the range 0 <= s < L - hashable = concatB(R.toRawBytes(), A.toRawBytes(), msg); // dom2(F, C) || R || A || PH(M) - } - catch (error) { } - const finish = (hashed) => { - if (SB == null) - return false; // false if try-catch catched an error - if (!zip215 && A.isSmallOrder()) - return false; // false for SBS: Strongly Binding Signature - const k = modL_LE(hashed); // decode in little-endian, modulo L - const RkA = R.add(A.mul(k, false)); // [8]R + [8][k]A' - return RkA.add(SB.negate()).clearCofactor().is0(); // [8][S]B = [8]R + [8][k]A' - }; - return { hashable, finish }; -}; -// RFC8032 5.1.7: verification async, sync -const verifyAsync = async (s, m, p, opts = dvo) => hashFinish(true, _verify(s, m, p, opts)); -const verify = (s, m, p, opts = dvo) => hashFinish(false, _verify(s, m, p, opts)); -const cr = () => // We support: 1) browsers 2) node.js 19+ - typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined; -const etc = { - bytesToHex: b2h, hexToBytes: h2b, concatBytes: concatB, - mod, invert, - randomBytes: (len = 32) => { - const crypto = cr(); // Can be shimmed in node.js <= 18 to prevent error: - // import { webcrypto } from 'node:crypto'; - // if (!globalThis.crypto) globalThis.crypto = webcrypto; - if (!crypto || !crypto.getRandomValues) - err('crypto.getRandomValues must be defined'); - return crypto.getRandomValues(u8n(len)); - }, - sha512Async: async (...messages) => { - const crypto = cr(); - if (!crypto || !crypto.subtle) - err('crypto.subtle or etc.sha512Async must be defined'); - const m = concatB(...messages); - return u8n(await crypto.subtle.digest('SHA-512', m.buffer)); - }, - sha512Sync: undefined, // Actual logic below -}; -Object.defineProperties(etc, { sha512Sync: { - configurable: false, get() { return _shaS; }, set(f) { if (!_shaS) - _shaS = f; }, - } }); -const utils = { - getExtendedPublicKeyAsync, getExtendedPublicKey, - randomPrivateKey: () => etc.randomBytes(32), - precompute(w = 8, p = G) { p.multiply(3n); w; return p; }, // no-op -}; -const W = 8; // Precomputes-related code. W = window size -const precompute = () => { - const points = []; // 10x sign(), 2x verify(). To achieve this, - const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate - let p = G, b = p; // a lot of points related to base point G. - for (let w = 0; w < windows; w++) { // Points are stored in array and used - b = p; // any time Gx multiplication is done. - points.push(b); // They consume 16-32 MiB of RAM. - for (let i = 1; i < 2 ** (W - 1); i++) { - b = b.add(p); - points.push(b); - } - p = b.double(); // Precomputes don't speed-up getSharedKey, - } // which multiplies user point by scalar, - return points; // when precomputes are using base point -}; -let Gpows = undefined; // precomputes for base point G -const wNAF = (n) => { - // Compared to other point mult methods, - const comp = Gpows || (Gpows = precompute()); // stores 2x less points using subtraction - const neg = (cnd, p) => { let n = p.negate(); return cnd ? n : p; }; // negate - let p = I, f = G; // f must be G, or could become I in the end - const windows = 1 + 256 / W; // W=8 17 windows - const wsize = 2 ** (W - 1); // W=8 128 window size - const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111 - const maxNum = 2 ** W; // W=8 256 - const shiftBy = BigInt(W); // W=8 8 - for (let w = 0; w < windows; w++) { - const off = w * wsize; - let wbits = Number(n & mask); // extract W bits. - n >>= shiftBy; // shift number by W bits. - if (wbits > wsize) { - wbits -= maxNum; - n += 1n; - } // split if bits > max: +224 => 256-32 - const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both - const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both - if (wbits === 0) { - f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point - } - else { // ^ can't add off2, off2 = I - p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point - } - } - return { p, f }; // return both real and fake points for JIT -}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul() -export { getPublicKey, getPublicKeyAsync, sign, verify, // Remove the export to easily use in REPL -signAsync, verifyAsync, CURVE, etc, utils, Point as ExtendedPoint }; // envs like browser console diff --git a/vendor/ows-signer/src/crypto/vendor/noble-secp256k1.js b/vendor/ows-signer/src/crypto/vendor/noble-secp256k1.js deleted file mode 100644 index e1156d4..0000000 --- a/vendor/ows-signer/src/crypto/vendor/noble-secp256k1.js +++ /dev/null @@ -1,602 +0,0 @@ -/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ -/** - * 4KB JS implementation of secp256k1 signatures & ECDH. Compliant with RFC6979. - * @module - */ -const B256 = 2n ** 256n; -const P = B256 - 0x1000003d1n; // curve's field prime -const N = B256 - 0x14551231950b75fc4402da1732fc9bebfn; // curve (group) order -const Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n; // base point x -const Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n; // base point y -/** - * secp256k1 curve parameters. Equation is x³ + ax + b. - * Gx and Gy are generator coordinates. p is field order, n is group order. - */ -const CURVE = { - p: P, n: N, a: 0n, b: 7n, Gx, Gy -}; // exported variables incl. a, b -const fLen = 32; // field / group byte length -const curve = (x) => M(M(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; a=0 -const err = (m = '') => { throw new Error(m); }; // error helper, messes-up stack trace -const isB = (n) => typeof n === 'bigint'; // is big integer -const isS = (s) => typeof s === 'string'; // is string -const fe = (n) => isB(n) && 0n < n && n < P; // is field element (invertible) -const ge = (n) => isB(n) && 0n < n && n < N; // is group element -const isu8 = (a) => (a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array')); -const au8 = (a, l) => // assert is Uint8Array (of specific length) - !isu8(a) || (typeof l === 'number' && l > 0 && a.length !== l) ? - err('Uint8Array expected') : a; -const u8n = (data) => new Uint8Array(data); // creates Uint8Array -const toU8 = (a, len) => au8(isS(a) ? h2b(a) : u8n(au8(a)), len); // norm(hex/u8a) to u8a -const M = (a, b = P) => { - const r = a % b; - return r >= 0n ? r : b + r; -}; -const aPoint = (p) => (p instanceof Point ? p : err('Point expected')); // is 3d point -/** Point in 3d xyz projective coordinates. 3d takes less inversions than 2d. */ -class Point { - constructor(px, py, pz) { - this.px = px; - this.py = py; - this.pz = pz; - Object.freeze(this); - } - /** Create 3d xyz point from 2d xy. (0, 0) => (0, 1, 0), not (0, 0, 1) */ - static fromAffine(p) { - return ((p.x === 0n) && (p.y === 0n)) ? I : new Point(p.x, p.y, 1n); - } - /** Convert Uint8Array or hex string to Point. */ - static fromHex(hex) { - hex = toU8(hex); // convert hex string to Uint8Array - let p = undefined; - const head = hex[0], tail = hex.subarray(1); // first byte is prefix, rest is data - const x = slc(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate - if (len === 33 && [0x02, 0x03].includes(head)) { // compressed points: 33b, start - if (!fe(x)) - err('Point hex invalid: x not FE'); // with byte 0x02 or 0x03. Check if 0 0n; d = d.double(), n >>= 1n) { // double-and-add ladder - if (n & 1n) - p = p.add(d); // if bit is present, add to point - else if (safe) - f = f.add(d); // if not, add to fake for timing safety - } - return p; - } - mulAddQUns(R, u1, u2) { - return this.mul(u1, false).add(R.mul(u2, false)).ok(); // Unsafe: do NOT use for stuff related - } // to private keys. Doesn't use Shamir trick - /** Convert point to 2d xy affine point. (x, y, z) ∋ (x=x/z, y=y/z) */ - toAffine() { - const { px: x, py: y, pz: z } = this; - if (this.equals(I)) - return { x: 0n, y: 0n }; // fast-path for zero point - if (z === 1n) - return { x, y }; // if z is 1, pass affine coordinates as-is - const iz = inv(z, P); // z^-1: invert z - if (M(z * iz) !== 1n) - err('inverse invalid'); // (z * z^-1) must be 1, otherwise bad math - return { x: M(x * iz), y: M(y * iz) }; // x = x*z^-1; y = y*z^-1 - } - /** Checks if the point is valid and on-curve. */ - assertValidity() { - const { x, y } = this.aff(); // convert to 2d xy affine point. - if (!fe(x) || !fe(y)) - err('Point invalid: x or y'); // x and y must be in range 0 < n < P - return M(y * y) === curve(x) ? // y² = x³ + ax + b, must be equal - this : err('Point invalid: not on curve'); - } - multiply(n) { return this.mul(n); } // Aliases to compress code - aff() { return this.toAffine(); } - ok() { return this.assertValidity(); } - toHex(isCompressed = true) { - const { x, y } = this.aff(); // convert to 2d xy affine point - const head = isCompressed ? ((y & 1n) === 0n ? '02' : '03') : '04'; // 0x02, 0x03, 0x04 prefix - return head + n2h(x) + (isCompressed ? '' : n2h(y)); // prefix||x and ||y - } - toRawBytes(isCompressed = true) { - return h2b(this.toHex(isCompressed)); // re-use toHex(), convert hex to bytes - } -} -/** Generator / base point */ -Point.BASE = new Point(Gx, Gy, 1n); -/** Identity / zero point */ -Point.ZERO = new Point(0n, 1n, 0n); -const { BASE: G, ZERO: I } = Point; // Generator, identity points -const padh = (n, pad) => n.toString(16).padStart(pad, '0'); -const b2h = (b) => Array.from(au8(b)).map(e => padh(e, 2)).join(''); // bytes to hex -const C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 }; // ASCII characters -const _ch = (ch) => { - if (ch >= C._0 && ch <= C._9) - return ch - C._0; // '2' => 50-48 - if (ch >= C.A && ch <= C.F) - return ch - (C.A - 10); // 'B' => 66-(65-10) - if (ch >= C.a && ch <= C.f) - return ch - (C.a - 10); // 'b' => 98-(97-10) - return; -}; -const h2b = (hex) => { - const e = 'hex invalid'; - if (!isS(hex)) - return err(e); - const hl = hex.length, al = hl / 2; - if (hl % 2) - return err(e); - const array = u8n(al); - for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { // treat each char as ASCII - const n1 = _ch(hex.charCodeAt(hi)); // parse first char, multiply it by 16 - const n2 = _ch(hex.charCodeAt(hi + 1)); // parse second char - if (n1 === undefined || n2 === undefined) - return err(e); - array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9 - } - return array; -}; -const b2n = (b) => BigInt('0x' + (b2h(b) || '0')); // bytes to number -const slc = (b, from, to) => b2n(b.slice(from, to)); // slice bytes num -const n2b = (num) => { - return isB(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected'); -}; -const n2h = (num) => b2h(n2b(num)); // number to 32b hex -const concatB = (...arrs) => { - const r = u8n(arrs.reduce((sum, a) => sum + au8(a).length, 0)); // create u8a of summed length - let pad = 0; // walk through each array, - arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type - return r; -}; -const inv = (num, md) => { - if (num === 0n || md <= 0n) - err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now - let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n; - while (a !== 0n) { // uses euclidean gcd algorithm - const q = b / a, r = b % a; // not constant-time - const m = x - u * q, n = y - v * q; - b = a, a = r, x = u, y = v, u = m, v = n; - } - return b === 1n ? M(x, md) : err('no inverse'); // b is gcd at this point -}; -const sqrt = (n) => { - let r = 1n; // So, a special, fast case. Paper: "Square Roots from 1;24,51,10 to Dan Shanks". - for (let num = n, e = (P + 1n) / 4n; e > 0n; e >>= 1n) { // powMod: modular exponentiation. - if (e & 1n) - r = (r * num) % P; // Uses exponentiation by squaring. - num = (num * num) % P; // Not constant-time. - } - return M(r * r) === n ? r : err('sqrt invalid'); // check if result is valid -}; -const toPriv = (p) => { - if (!isB(p)) - p = b2n(toU8(p, fLen)); // convert to bigint when bytes - return ge(p) ? p : err('private key invalid 3'); // check if bigint is in range -}; -const high = (n) => n > (N >> 1n); // if a number is bigger than CURVE.n/2 -/** Creates 33/65-byte public key from 32-byte private key. */ -const getPublicKey = (privKey, isCompressed = true) => { - return Point.fromPrivateKey(privKey).toRawBytes(isCompressed); -}; -/** ECDSA Signature class. Supports only compact 64-byte representation, not DER. */ -class Signature { - constructor(r, s, recovery) { - this.r = r; - this.s = s; - this.recovery = recovery; - this.assertValidity(); // recovery bit is optional when - } // constructed outside. - /** Create signature from 64b compact (r || s) representation. */ - static fromCompact(hex) { - hex = toU8(hex, 64); // compact repr is (32b r)||(32b s) - return new Signature(slc(hex, 0, fLen), slc(hex, fLen, 2 * fLen)); - } - assertValidity() { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n - /** Create new signature, with added recovery bit. */ - addRecoveryBit(rec) { - return new Signature(this.r, this.s, rec); - } - hasHighS() { return high(this.s); } - normalizeS() { - return high(this.s) ? new Signature(this.r, M(-this.s, N), this.recovery) : this; - } - /** ECDSA public key recovery. Requires msg hash and recovery id. */ - recoverPublicKey(msgh) { - const { r, s, recovery: rec } = this; // secg.org/sec1-v2.pdf 4.1.6 - if (![0, 1, 2, 3].includes(rec)) - err('recovery id invalid'); // check recovery id - const h = bits2int_modN(toU8(msgh, fLen)); // Truncate hash - const radj = rec === 2 || rec === 3 ? r + N : r; // If rec was 2 or 3, q.x is bigger than n - if (radj >= P) - err('q.x invalid'); // ensure q.x is still a field element - const head = (rec & 1) === 0 ? '02' : '03'; // head is 0x02 or 0x03 - const R = Point.fromHex(head + n2h(radj)); // concat head + hex repr of r - const ir = inv(radj, N); // r^-1 - const u1 = M(-h * ir, N); // -hr^-1 - const u2 = M(s * ir, N); // sr^-1 - return G.mulAddQUns(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1) - } - /** Uint8Array 64b compact (r || s) representation. */ - toCompactRawBytes() { return h2b(this.toCompactHex()); } - /** Hex string 64b compact (r || s) representation. */ - toCompactHex() { return n2h(this.r) + n2h(this.s); } -} -const bits2int = (bytes) => { - const delta = bytes.length * 8 - 256; // RFC suggests optional truncating via bits2octets - if (delta > 1024) - err('msg invalid'); // our CUSTOM check, "just-in-case" - const num = b2n(bytes); // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which - return delta > 0 ? num >> BigInt(delta) : num; // matches bits2int. bits2int can produce res>N. -}; -const bits2int_modN = (bytes) => { - return M(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors -}; -const i2o = (num) => n2b(num); // int to octets -const cr = () => // We support: 1) browsers 2) node.js 19+ 3) deno, other envs with crypto - typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined; -let _hmacSync; // Can be redefined by use in utils; built-ins don't provide it -const optS = { lowS: true }; // opts for sign() -const optV = { lowS: true }; // standard opts for verify() -const prepSig = (msgh, priv, opts = optS) => { - if (['der', 'recovered', 'canonical'].some(k => k in opts)) - err('option not supported'); // legacy opts - let { lowS } = opts; // generates low-s sigs by default - if (lowS == null) - lowS = true; // RFC6979 3.2: we skip step A - const h1i = bits2int_modN(toU8(msgh)); // msg bigint - const h1o = i2o(h1i); // msg octets - const d = toPriv(priv); // validate private key, convert to bigint - const seed = [i2o(d), h1o]; // Step D of RFC6979 3.2 - let ent = opts.extraEntropy; // RFC6979 3.6: additional k' (optional) - if (ent) // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') - seed.push(ent === true ? etc.randomBytes(fLen) : toU8(ent)); // true == fetch from CSPRNG - const m = h1i; // convert msg to bigint - const k2sig = (kBytes) => { - const k = bits2int(kBytes); // RFC6979 method. - if (!ge(k)) - return; // Check 0 < k < CURVE.n - const ik = inv(k, N); // k^-1 mod n, NOT mod P - const q = G.mul(k).aff(); // q = Gk - const r = M(q.x, N); // r = q.x mod n - if (r === 0n) - return; // r=0 invalid - const s = M(ik * M(m + M(d * r, N), N), N); // s = k^-1(m + rd) mod n - if (s === 0n) - return; // s=0 invalid - let normS = s; // normalized S - let rec = (q.x === r ? 0 : 2) | Number(q.y & 1n); // recovery bit - if (lowS && high(s)) { // if lowS was passed, ensure s is always - normS = M(-s, N); // in the bottom half of CURVE.n - rec ^= 1; - } - return new Signature(r, normS, rec); // use normS, not s - }; - return { seed: concatB(...seed), k2sig }; -}; -function hmacDrbg(asynchronous) { - let v = u8n(fLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs. - let k = u8n(fLen); // Steps B, C of RFC6979 3.2: set hashLen, in our case always same - let i = 0; // Iterations counter, will throw when over 1000 - const reset = () => { v.fill(1); k.fill(0); i = 0; }; - const _e = 'drbg: tried 1000 values'; - if (asynchronous) { // asynchronous=true - const h = (...b) => etc.hmacSha256Async(k, v, ...b); // hmac(k)(v, ...values) - const reseed = async (seed = u8n()) => { - k = await h(u8n([0x00]), seed); // k = hmac(K || V || 0x00 || seed) - v = await h(); // v = hmac(K || V) - if (seed.length === 0) - return; - k = await h(u8n([0x01]), seed); // k = hmac(K || V || 0x01 || seed) - v = await h(); // v = hmac(K || V) - }; - const gen = async () => { - if (i++ >= 1000) - err(_e); - v = await h(); // v = hmac(K || V) - return v; - }; - return async (seed, pred) => { - reset(); // the returned fn, don't, it's: 1. slower (JIT). 2. unsafe (async race conditions) - await reseed(seed); // Steps D-G - let res = undefined; // Step H: grind until k is in [1..n-1] - while (!(res = pred(await gen()))) - await reseed(); // test predicate until it returns ok - reset(); - return res; - }; - } - else { - const h = (...b) => { - const f = _hmacSync; - if (!f) - err('etc.hmacSha256Sync not set'); - return f(k, v, ...b); // hmac(k)(v, ...values) - }; - const reseed = (seed = u8n()) => { - k = h(u8n([0x00]), seed); // k = hmac(k || v || 0x00 || seed) - v = h(); // v = hmac(k || v) - if (seed.length === 0) - return; - k = h(u8n([0x01]), seed); // k = hmac(k || v || 0x01 || seed) - v = h(); // v = hmac(k || v) - }; - const gen = () => { - if (i++ >= 1000) - err(_e); - v = h(); // v = hmac(k || v) - return v; - }; - return (seed, pred) => { - reset(); - reseed(seed); // Steps D-G - let res = undefined; // Step H: grind until k is in [1..n-1] - while (!(res = pred(gen()))) - reseed(); // test predicate until it returns ok - reset(); - return res; - }; - } -} -; -/** ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k. */ -/** - * Sign a msg hash using secp256k1. Async. - * It is advised to use `extraEntropy: true` (from RFC6979 3.6) to prevent fault attacks. - * Worst case: if randomness source for extraEntropy is bad, it would be as secure as if - * the option has not been used. - * @param msgh - message HASH, not message itself e.g. sha256(message) - * @param priv - private key - * @param opts - `lowS: true` to prevent malleability (s >= CURVE.n/2), `extraEntropy: boolean | Hex` to improve sig security. - */ -const signAsync = async (msgh, priv, opts = optS) => { - const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg - return hmacDrbg(true)(seed, k2sig); // Re-run drbg until k2sig returns ok -}; -/** - * Sign a msg hash using secp256k1. - * It is advised to use `extraEntropy: true` (from RFC6979 3.6) to prevent fault attacks. - * Worst case: if randomness source for extraEntropy is bad, it would be as secure as if - * the option has not been used. - * @param msgh - message HASH, not message itself e.g. sha256(message) - * @param priv - private key - * @param opts - `lowS: true` to prevent malleability (s >= CURVE.n/2), `extraEntropy: boolean | Hex` to improve sig security. - * @example - * const sig = sign(sha256('hello'), privKey, { extraEntropy: true }).toCompactRawBytes(); - */ -const sign = (msgh, priv, opts = optS) => { - const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg - return hmacDrbg(false)(seed, k2sig); // Re-run drbg until k2sig returns ok -}; -/** - * Verify a signature using secp256k1. - * @param sig - signature, 64-byte or Signature instance - * @param msgh - message HASH, not message itself e.g. sha256(message) - * @param pub - public key - * @param opts - { lowS: true } is default, prohibits s >= CURVE.n/2 to prevent malleability - */ -const verify = (sig, msgh, pub, opts = optV) => { - let { lowS } = opts; // ECDSA signature verification - if (lowS == null) - lowS = true; // Default lowS=true - if ('strict' in opts) - err('option not supported'); // legacy param - let sig_, h, P; // secg.org/sec1-v2.pdf 4.1.4 - const rs = sig && typeof sig === 'object' && 'r' in sig; // Previous ver supported DER sigs. We - if (!rs && (toU8(sig).length !== 2 * fLen)) // throw error when DER is suspected now. - err('signature must be 64 bytes'); - try { - sig_ = rs ? new Signature(sig.r, sig.s).assertValidity() : Signature.fromCompact(sig); - h = bits2int_modN(toU8(msgh)); // Truncate hash - P = pub instanceof Point ? pub.ok() : Point.fromHex(pub); // Validate public key - } - catch (e) { - return false; - } // Check sig for validity in both cases - if (!sig_) - return false; - const { r, s } = sig_; - if (lowS && high(s)) - return false; // lowS bans sig.s >= CURVE.n/2 - let R; - try { - const is = inv(s, N); // s^-1 - const u1 = M(h * is, N); // u1 = hs^-1 mod n - const u2 = M(r * is, N); // u2 = rs^-1 mod n - R = G.mulAddQUns(P, u1, u2).aff(); // R = u1⋅G + u2⋅P - } - catch (error) { - return false; - } - if (!R) - return false; // stop if R is identity / zero point - const v = M(R.x, N); // R.x must be in N's field, not P's - return v === r; // mod(R.x, n) == r -}; -/** - * Elliptic Curve Diffie-Hellman (ECDH) on secp256k1. - * Result is **NOT hashed**. Use hash on it if you need. - * @param privA private key A - * @param pubB public key B - * @param isCompressed 33-byte or 65-byte output - * @returns public key C - */ -const getSharedSecret = (privA, pubB, isCompressed = true) => { - return Point.fromHex(pubB).mul(toPriv(privA)).toRawBytes(isCompressed); // ECDH -}; -const hashToPrivateKey = (hash) => { - hash = toU8(hash); // produces private keys with modulo bias - if (hash.length < fLen + 8 || hash.length > 1024) - err('expected 40-1024b'); // being neglible. - const num = M(b2n(hash), N - 1n); // takes n+8 bytes - return n2b(num + 1n); // returns (hash mod n-1)+1 -}; -/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */ -const etc = { - hexToBytes: h2b, - bytesToHex: b2h, - concatBytes: concatB, - bytesToNumberBE: b2n, - numberToBytesBE: n2b, - mod: M, - invert: inv, // math utilities - hmacSha256Async: async (key, ...msgs) => { - const c = cr(); // async HMAC-SHA256, no sync built-in! - const s = c && c.subtle; // For React Native support, see README. - if (!s) - return err('etc.hmacSha256Async or crypto.subtle must be defined'); // Uses webcrypto built-in cryptography. - const k = await s.importKey('raw', key, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']); - return u8n(await s.sign('HMAC', k, concatB(...msgs))); - }, - hmacSha256Sync: _hmacSync, // For TypeScript. Actual logic is below - hashToPrivateKey: hashToPrivateKey, - randomBytes: (len = 32) => { - const crypto = cr(); // Must be shimmed in node.js <= 18 to prevent error. See README. - if (!crypto || !crypto.getRandomValues) - err('crypto.getRandomValues must be defined'); - return crypto.getRandomValues(u8n(len)); - }, -}; -/** Curve-specific utilities for private keys. */ -const utils = { - normPrivateKeyToScalar: toPriv, - isValidPrivateKey: (key) => { try { - return !!toPriv(key); - } - catch (e) { - return false; - } }, - randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(fLen + 16)), // FIPS 186 B.4.1. - precompute: (w = 8, p = G) => { p.multiply(3n); w; return p; }, // no-op -}; -Object.defineProperties(etc, { hmacSha256Sync: { - configurable: false, get() { return _hmacSync; }, set(f) { if (!_hmacSync) - _hmacSync = f; }, - } }); -const W = 8; // Precomputes-related code. W = window size -const precompute = () => { - const points = []; // 10x sign(), 2x verify(). To achieve this, - const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate - let p = G, b = p; // a lot of points related to base point G. - for (let w = 0; w < windows; w++) { // Points are stored in array and used - b = p; // any time Gx multiplication is done. - points.push(b); // They consume 16-32 MiB of RAM. - for (let i = 1; i < 2 ** (W - 1); i++) { - b = b.add(p); - points.push(b); - } - p = b.double(); // Precomputes don't speed-up getSharedKey, - } // which multiplies user point by scalar, - return points; // when precomputes are using base point -}; -let Gpows = undefined; // precomputes for base point G -const wNAF = (n) => { - // Compared to other point mult methods, - const comp = Gpows || (Gpows = precompute()); // stores 2x less points using subtraction - const neg = (cnd, p) => { let n = p.negate(); return cnd ? n : p; }; // negate - let p = I, f = G; // f must be G, or could become I in the end - const windows = 1 + 256 / W; // W=8 17 windows - const wsize = 2 ** (W - 1); // W=8 128 window size - const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111 - const maxNum = 2 ** W; // W=8 256 - const shiftBy = BigInt(W); // W=8 8 - for (let w = 0; w < windows; w++) { - const off = w * wsize; - let wbits = Number(n & mask); // extract W bits. - n >>= shiftBy; // shift number by W bits. - if (wbits > wsize) { - wbits -= maxNum; - n += 1n; - } // split if bits > max: +224 => 256-32 - const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both - const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both - if (wbits === 0) { - f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point - } - else { // ^ can't add off2, off2 = I - p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point - } - } - return { p, f }; // return both real and fake points for JIT -}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul() -export { getPublicKey, sign, signAsync, verify, CURVE, // Remove the export to easily use in REPL -getSharedSecret, etc, utils, Point as ProjectivePoint, Signature }; // envs like browser console diff --git a/vendor/ows-signer/src/debug.js b/vendor/ows-signer/src/debug.js deleted file mode 100644 index 25daf61..0000000 --- a/vendor/ows-signer/src/debug.js +++ /dev/null @@ -1,42 +0,0 @@ -const DEBUG_KEY = "ows-signer:debug"; - -/** @returns {boolean} */ -export function isDebug() { - if (typeof globalThis === "undefined") return false; - return ( - globalThis.OWS_SIGNER_DEBUG === true || - (typeof localStorage !== "undefined" && - localStorage.getItem(DEBUG_KEY) === "1") - ); -} - -/** @param {...unknown} args */ -export function debugLog(...args) { - if (isDebug()) { - console.info("[ows-signer]", ...args); - } -} - -/** - * @param {PublicKeyCredential} credential - * @returns {Record} - */ -export function describePrfExtensionResults(credential) { - const prf = credential.getClientExtensionResults()?.prf; - const raw = prf?.results?.first; - return { - prfEnabled: prf?.enabled, - hasResults: Boolean(prf?.results), - resultKeys: prf?.results ? Object.keys(prf.results) : [], - prfKeys: prf ? Object.keys(prf) : [], - rawFirstType: typeof raw, - rawFirstIsArrayBuffer: raw instanceof ArrayBuffer, - rawFirstIsView: ArrayBuffer.isView(raw), - rawFirstByteLength: - raw instanceof ArrayBuffer - ? raw.byteLength - : ArrayBuffer.isView(raw) - ? raw.byteLength - : null, - }; -} diff --git a/vendor/ows-signer/src/handlers.js b/vendor/ows-signer/src/handlers.js deleted file mode 100644 index 93bc4b0..0000000 --- a/vendor/ows-signer/src/handlers.js +++ /dev/null @@ -1,691 +0,0 @@ -import { - API_VERSION, - METHODS, - PRF_LABEL_ED25519, - SIGN_SCHEMES, - SIGNER_VERSION, -} from "./constants.js"; -import { - decryptAes256Batch, - encryptAes256Batch, -} from "./crypto/aes256.js"; -import { - deriveEd25519SeedFromPrf, - deriveKeysFromCredential, - hkdfExpand, - normalizePrfOutputToArrayBuffer, -} from "./crypto/prf.js"; -import { getPublicKeyAsync as edGetPublicKeyAsync } from "./crypto/vendor/noble-ed25519.js"; -import { getPublicKey as secpGetPublicKey } from "./crypto/vendor/noble-secp256k1.js"; -import { debugLog, describePrfExtensionResults } from "./debug.js"; -import { decryptPrivateKey, encryptPrivateKey } from "./crypto/recovery.js"; -import { signWithScheme, validateSignPayload } from "./crypto/sign.js"; -import { parse0xHex, to0xHex } from "./hex.js"; -import { emitEvent } from "./rpc.js"; -import { - clearRecoveryPrivateKey, - getRecoveryPrivateKey, - hasRecoverySession, - setRecoveryPrivateKey, - withCeremony, - zeroize, -} from "./state.js"; -import { clearUi, promptPassphrase, showPrivateKey } from "./ui.js"; -import { - createPasskeyCredential, - getAssertionSignatureBase64Url, - getCredentialId, - getPasskeyAssertion, - getPasskeyPublicKeyBase64Url, -} from "./webauthn.js"; - -/** - * @param {PublicKeyCredential} credential - * @returns {Promise} - */ -async function ed25519SeedFromCredential(credential) { - const prfRaw = /** @type {{ prf?: { results?: { first?: unknown } } }} */ ( - credential.getClientExtensionResults() - ).prf?.results?.first; - const prfBuf = normalizePrfOutputToArrayBuffer(prfRaw); - if (!prfBuf) throw new Error("noPrf"); - return deriveEd25519SeedFromPrf(prfBuf); -} - -/** - * @param {Uint8Array} secp256k1Scalar - * @returns {Promise} - */ -async function ed25519SeedFromSecp256k1Scalar(secp256k1Scalar) { - return hkdfExpand( - secp256k1Scalar.buffer.slice( - secp256k1Scalar.byteOffset, - secp256k1Scalar.byteOffset + secp256k1Scalar.byteLength, - ), - PRF_LABEL_ED25519, - 32, - ); -} - -/** - * @param {string} targetOrigin - * @param {string | undefined} correlationId - * @param {Uint8Array} secp256k1PublicKey - * @param {Uint8Array} ed25519PublicKey - */ -function emitKeyDerived( - targetOrigin, - correlationId, - secp256k1PublicKey, - ed25519PublicKey, -) { - emitEvent(window.parent, targetOrigin, "KeyDerived", correlationId, { - secp256k1PublicKey: to0xHex(secp256k1PublicKey), - ed25519PublicKey: to0xHex(ed25519PublicKey), - }); -} - -/** - * @param {string} method - * @param {Record} params - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -export async function handleRequest( - method, - params, - correlationId, - targetOrigin, -) { - if (!METHODS.includes(method)) { - emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, { - reason: "unknownMethod", - }); - return; - } - - try { - switch (method) { - case "getVersion": - emitEvent(window.parent, targetOrigin, "Version", correlationId, { - apiVersion: API_VERSION, - signerVersion: SIGNER_VERSION, - recoverySessionActive: hasRecoverySession(), - }); - return; - - case "createCredential": - await handleCreateCredential(params, correlationId, targetOrigin); - return; - - case "signDigest": - await handleSignDigest(params, correlationId, targetOrigin); - return; - - case "revealPrivateKey": - await handleRevealPrivateKey(params, correlationId, targetOrigin); - return; - - case "createRecoveryData": - await handleCreateRecoveryData(params, correlationId, targetOrigin); - return; - - case "recoverKey": - await handleRecoverKey(params, correlationId, targetOrigin); - return; - - case "getPublicKey": - await handleGetPublicKey(params, correlationId, targetOrigin); - return; - - case "clearRecoverySession": - clearRecoveryPrivateKey(); - clearUi(); - emitEvent( - window.parent, - targetOrigin, - "RecoverySessionCleared", - correlationId, - {}, - ); - return; - - case "encryptAES256": - await handleEncryptAES256(params, correlationId, targetOrigin); - return; - - case "decryptAES256": - await handleDecryptAES256(params, correlationId, targetOrigin); - return; - } - } catch (error) { - handleError(error, correlationId, targetOrigin); - } -} - -/** - * PRF bytes are returned on assertion (`prf.eval`), not registration (`prf.enable`). - * Some platforms may return results on create; otherwise run a follow-up get. - * - * @param {PublicKeyCredential} credential - * @param {string} [credentialId] - * @returns {Promise} - */ -async function credentialForKeyDerivation(credential, credentialId) { - debugLog("ceremony extension results", describePrfExtensionResults(credential)); - - const rawPrf = credential.getClientExtensionResults()?.prf?.results?.first; - if (normalizePrfOutputToArrayBuffer(rawPrf)) { - debugLog("using PRF output from current ceremony"); - return credential; - } - - const id = credentialId ?? getCredentialId(credential); - debugLog("no PRF results on registration; running assertion with prf.eval", { - credentialId: id, - }); - const assertion = await getPasskeyAssertion(undefined, id); - debugLog( - "assertion extension results", - describePrfExtensionResults(assertion), - ); - return assertion; -} - -/** - * @param {Record} params - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -async function handleCreateCredential(params, correlationId, targetOrigin) { - const name = params.name; - if (typeof name !== "string" || !name) { - emitInvalid(correlationId, targetOrigin, "invalidName"); - return; - } - const options = - params.options && typeof params.options === "object" - ? /** @type {{ rpName?: string, userDisplayName?: string, userId?: string }} */ ( - params.options - ) - : {}; - - await withCeremony(async () => { - const credential = await createPasskeyCredential(name, options); - const credentialId = getCredentialId(credential); - const prfCredential = await credentialForKeyDerivation( - credential, - credentialId, - ); - const keys = await deriveKeysFromCredential(prfCredential); - emitKeyDerived( - targetOrigin, - correlationId, - keys.secp256k1PublicKey, - keys.ed25519PublicKey, - ); - - emitEvent(window.parent, targetOrigin, "CredentialCreated", correlationId, { - credentialId, - passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential), - secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey), - }); - zeroize(keys.secp256k1PrivateKey); - }); -} - -/** - * @param {Record} params - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -async function handleSignDigest(params, correlationId, targetOrigin) { - const digestData = params.digestData; - const scheme = params.scheme; - const credentialId = - typeof params.credentialId === "string" ? params.credentialId : undefined; - - if (typeof digestData !== "string" || typeof scheme !== "string") { - emitInvalid(correlationId, targetOrigin, "invalidParams"); - return; - } - if ( - !SIGN_SCHEMES.includes( - /** @type {import('./constants.js').SignScheme} */ (scheme), - ) - ) { - emitInvalid(correlationId, targetOrigin, "unknownScheme"); - return; - } - - const digest = parse0xHex(digestData); - validateSignPayload( - /** @type {import('./constants.js').SignScheme} */ (scheme), - digest, - ); - - if (hasRecoverySession()) { - const cached = getRecoveryPrivateKey(); - if (!cached) throw new Error("recoverySessionEmpty"); - const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(cached); - const signature = await signWithScheme( - /** @type {import('./constants.js').SignScheme} */ (scheme), - digest, - cached, - ed25519Seed, - ); - zeroize(ed25519Seed); - emitEvent(window.parent, targetOrigin, "DigestSigned", correlationId, { - digest: digestData, - scheme, - signature, - credentialId: credentialId ?? null, - }); - return; - } - - await withCeremony(async () => { - const credential = await getPasskeyAssertion(undefined, credentialId); - const keys = await deriveKeysFromCredential(credential); - const ed25519Seed = await ed25519SeedFromCredential(credential); - emitKeyDerived( - targetOrigin, - correlationId, - keys.secp256k1PublicKey, - keys.ed25519PublicKey, - ); - - const signature = await signWithScheme( - /** @type {import('./constants.js').SignScheme} */ (scheme), - digest, - keys.secp256k1PrivateKey, - ed25519Seed, - ); - zeroize(keys.secp256k1PrivateKey); - zeroize(ed25519Seed); - - emitEvent(window.parent, targetOrigin, "DigestSigned", correlationId, { - digest: digestData, - scheme, - signature, - credentialId: credentialId ?? getCredentialId(credential), - }); - }); -} - -/** - * @param {Record} params - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -async function handleRevealPrivateKey(params, correlationId, targetOrigin) { - const credentialId = - typeof params.credentialId === "string" ? params.credentialId : undefined; - - await withCeremony(async () => { - const credential = await getPasskeyAssertion(undefined, credentialId); - const keys = await deriveKeysFromCredential(credential); - emitKeyDerived( - targetOrigin, - correlationId, - keys.secp256k1PublicKey, - keys.ed25519PublicKey, - ); - showPrivateKey(keys.secp256k1PrivateKey); - zeroize(keys.secp256k1PrivateKey); - }); -} - -/** - * @param {Record} params - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -async function handleCreateRecoveryData(params, correlationId, targetOrigin) { - const passwordText = params.passwordText; - const buttonText = params.buttonText; - const minPasswordLength = params.minPasswordLength; - const credentialId = - typeof params.credentialId === "string" ? params.credentialId : undefined; - - if ( - typeof passwordText !== "string" || - typeof buttonText !== "string" || - typeof minPasswordLength !== "number" - ) { - emitInvalid(correlationId, targetOrigin, "invalidParams"); - return; - } - - const passphrase = await promptPassphrase( - passwordText, - buttonText, - minPasswordLength, - ); - - // Recovery session already holds the secp256k1 scalar — no WebAuthn needed. - if (hasRecoverySession()) { - const cached = getRecoveryPrivateKey(); - if (!cached) throw new Error("recoverySessionEmpty"); - const secp256k1PublicKey = secpGetPublicKey(cached, false); - const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(cached); - const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed); - emitKeyDerived( - targetOrigin, - correlationId, - secp256k1PublicKey, - ed25519PublicKey, - ); - const encryptedPrivateKey = await encryptPrivateKey(cached, passphrase); - zeroize(ed25519Seed); - clearUi(); - emitEvent( - window.parent, - targetOrigin, - "RecoveryDataCreated", - correlationId, - { encryptedPrivateKey }, - ); - return; - } - - await withCeremony(async () => { - const credential = await getPasskeyAssertion(undefined, credentialId); - const keys = await deriveKeysFromCredential(credential); - emitKeyDerived( - targetOrigin, - correlationId, - keys.secp256k1PublicKey, - keys.ed25519PublicKey, - ); - const encryptedPrivateKey = await encryptPrivateKey( - keys.secp256k1PrivateKey, - passphrase, - ); - zeroize(keys.secp256k1PrivateKey); - emitEvent( - window.parent, - targetOrigin, - "RecoveryDataCreated", - correlationId, - { encryptedPrivateKey }, - ); - }); -} - -/** - * @param {Record} params - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -async function handleRecoverKey(params, correlationId, targetOrigin) { - const envelope = params.aes256EncryptedPrivateKey; - const passwordText = params.passwordText; - const buttonText = params.buttonText; - const credentialId = - typeof params.credentialId === "string" ? params.credentialId : undefined; - - if ( - typeof envelope !== "string" || - typeof passwordText !== "string" || - typeof buttonText !== "string" - ) { - emitInvalid(correlationId, targetOrigin, "invalidParams"); - return; - } - - const passphrase = await promptPassphrase(passwordText, buttonText, 1); - const privateKey = await decryptPrivateKey(envelope, passphrase); - setRecoveryPrivateKey(privateKey); - - // Emit public keys so OWSSigner can cache addresses (uncompressed secp256k1 — - // viem publicKeyToAddress requires 0x04 ‖ X ‖ Y). - const secp256k1PublicKey = secpGetPublicKey(privateKey, false); - const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(privateKey); - const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed); - emitKeyDerived( - targetOrigin, - correlationId, - secp256k1PublicKey, - ed25519PublicKey, - ); - zeroize(ed25519Seed); - - showPrivateKey(privateKey); - - if (credentialId) { - await withCeremony(async () => { - await getPasskeyAssertion(undefined, credentialId); - clearRecoveryPrivateKey(); - }); - emitEvent( - window.parent, - targetOrigin, - "RecoverySessionCleared", - correlationId, - { rebound: true }, - ); - return; - } - - emitEvent( - window.parent, - targetOrigin, - "RecoverySessionStarted", - correlationId, - { recoverySessionActive: true }, - ); -} - -/** - * @param {unknown} value - * @returns {value is string[]} - */ -function isStringArray(value) { - return ( - Array.isArray(value) && value.every((item) => typeof item === "string") - ); -} - -/** - * Batch AES-256-GCM seal using the secp256k1 scalar (same as `signDigest`). - * - * @param {Record} params - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -async function handleEncryptAES256(params, correlationId, targetOrigin) { - const plaintexts = params.plaintexts; - const credentialId = - typeof params.credentialId === "string" ? params.credentialId : undefined; - - if (!isStringArray(plaintexts)) { - emitInvalid(correlationId, targetOrigin, "invalidParams"); - return; - } - - if (plaintexts.length === 0) { - emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, { - ciphertexts: [], - }); - return; - } - - if (hasRecoverySession()) { - const cached = getRecoveryPrivateKey(); - if (!cached) throw new Error("recoverySessionEmpty"); - const ciphertexts = await encryptAes256Batch(plaintexts, cached); - emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, { - ciphertexts, - }); - return; - } - - await withCeremony(async () => { - const credential = await getPasskeyAssertion(undefined, credentialId); - const keys = await deriveKeysFromCredential(credential); - emitKeyDerived( - targetOrigin, - correlationId, - keys.secp256k1PublicKey, - keys.ed25519PublicKey, - ); - try { - const ciphertexts = await encryptAes256Batch( - plaintexts, - keys.secp256k1PrivateKey, - ); - emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, { - ciphertexts, - }); - } finally { - zeroize(keys.secp256k1PrivateKey); - } - }); -} - -/** - * Batch AES-256-GCM unseal using the secp256k1 scalar (same as `signDigest`). - * - * @param {Record} params - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -async function handleDecryptAES256(params, correlationId, targetOrigin) { - const ciphertexts = params.ciphertexts; - const credentialId = - typeof params.credentialId === "string" ? params.credentialId : undefined; - - if (!isStringArray(ciphertexts)) { - emitInvalid(correlationId, targetOrigin, "invalidParams"); - return; - } - - if (ciphertexts.length === 0) { - emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, { - plaintexts: [], - }); - return; - } - - if (hasRecoverySession()) { - const cached = getRecoveryPrivateKey(); - if (!cached) throw new Error("recoverySessionEmpty"); - const plaintexts = await decryptAes256Batch(ciphertexts, cached); - emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, { - plaintexts, - }); - return; - } - - await withCeremony(async () => { - const credential = await getPasskeyAssertion(undefined, credentialId); - const keys = await deriveKeysFromCredential(credential); - emitKeyDerived( - targetOrigin, - correlationId, - keys.secp256k1PublicKey, - keys.ed25519PublicKey, - ); - try { - const plaintexts = await decryptAes256Batch( - ciphertexts, - keys.secp256k1PrivateKey, - ); - emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, { - plaintexts, - }); - } finally { - zeroize(keys.secp256k1PrivateKey); - } - }); -} - -/** - * @param {Record} params - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -async function handleGetPublicKey(params, correlationId, targetOrigin) { - const credentialId = - typeof params.credentialId === "string" ? params.credentialId : undefined; - const challenge = - typeof params.challenge === "string" - ? parse0xHex(params.challenge) - : undefined; - - await withCeremony(async () => { - const credential = await getPasskeyAssertion(challenge, credentialId); - const keys = await deriveKeysFromCredential(credential); - emitKeyDerived( - targetOrigin, - correlationId, - keys.secp256k1PublicKey, - keys.ed25519PublicKey, - ); - - emitEvent(window.parent, targetOrigin, "PublicKey", correlationId, { - credentialId: getCredentialId(credential), - passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential), - secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey), - ed25519PublicKey: to0xHex(keys.ed25519PublicKey), - }); - - if (challenge) { - const signature = getAssertionSignatureBase64Url(credential); - if (signature) { - emitEvent( - window.parent, - targetOrigin, - "ChallengeSigned", - correlationId, - { - challenge: to0xHex(challenge), - signature, - }, - ); - } - } - - zeroize(keys.secp256k1PrivateKey); - }); -} - -/** - * @param {unknown} error - * @param {string | undefined} correlationId - * @param {string} targetOrigin - */ -function handleError(error, correlationId, targetOrigin) { - const name = error instanceof Error ? error.name : ""; - const message = error instanceof Error ? error.message : String(error); - - if (name === "NotAllowedError" || message.includes("NotAllowed")) { - emitEvent(window.parent, targetOrigin, "NotAllowed", correlationId, { - reason: message || "notAllowed", - }); - return; - } - - if (message === "ceremonyInProgress") { - emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, { - reason: "ceremonyInProgress", - }); - return; - } - - emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, { - reason: message || "error", - }); -} - -/** - * @param {string | undefined} correlationId - * @param {string} targetOrigin - * @param {string} reason - */ -function emitInvalid(correlationId, targetOrigin, reason) { - emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, { - reason, - }); -} diff --git a/vendor/ows-signer/src/hex.js b/vendor/ows-signer/src/hex.js deleted file mode 100644 index 96d9e08..0000000 --- a/vendor/ows-signer/src/hex.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @param {string} hex - with or without `0x` prefix - * @returns {Uint8Array} - */ -export function hexToBytes(hex) { - if (hex.startsWith("0x") || hex.startsWith("0X")) { - hex = hex.slice(2); - } - const out = new Uint8Array(hex.length / 2); - for (let i = 0; i < out.length; i++) { - out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16); - } - return out; -} - -/** - * @param {unknown} value - * @param {number} [expectedBytes] - * @returns {Uint8Array} - */ -export function parse0xHex(value, expectedBytes) { - if (typeof value !== "string" || !value.startsWith("0x")) { - throw new Error("Expected 0x-prefixed hex string"); - } - const hex = value.slice(2); - if (hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) { - throw new Error("Invalid hex string"); - } - const bytes = hexToBytes(hex); - if (expectedBytes !== undefined && bytes.length !== expectedBytes) { - throw new Error(`Expected ${expectedBytes} bytes, got ${bytes.length}`); - } - return bytes; -} - -/** - * @param {Uint8Array} bytes - * @returns {`0x${string}`} - */ -export function to0xHex(bytes) { - return `0x${Array.from(bytes) - .map((b) => b.toString(16).padStart(2, "0")) - .join("")}`; -} - -/** - * @param {ArrayBuffer | Uint8Array} buffer - * @returns {string} - */ -export function bufferToBase64Url(buffer) { - const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); - let binary = ""; - for (const b of bytes) binary += String.fromCharCode(b); - return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); -} - -/** - * @param {string} value - * @returns {Uint8Array} - */ -export function base64UrlToBytes(value) { - const pad = "=".repeat((4 - (value.length % 4)) % 4); - const b64 = value.replace(/-/g, "+").replace(/_/g, "/") + pad; - const binary = atob(b64); - const out = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i); - return out; -} diff --git a/vendor/ows-signer/src/main.js b/vendor/ows-signer/src/main.js deleted file mode 100644 index 13ba833..0000000 --- a/vendor/ows-signer/src/main.js +++ /dev/null @@ -1,40 +0,0 @@ -import { handleRequest } from "./handlers.js"; -import { - isValidNesting, - setTrustedParentOrigin, -} from "./state.js"; -import { initUi } from "./ui.js"; -import { isValidParentMessage, parseRequest } from "./rpc.js"; - -function bootstrap() { - const root = document.getElementById("ows-signer-root"); - if (!root) { - throw new Error("OWS signer root element missing"); - } - initUi(root); - - if (!isValidNesting()) { - console.error("[ows-signer] Must be embedded in a wallet iframe (double iframe)."); - return; - } - - window.addEventListener("message", async (event) => { - if (!isValidParentMessage(event)) return; - if (!parseRequest(event.data)) return; - - setTrustedParentOrigin(event.origin); - const targetOrigin = event.origin; - const { method, correlationId, params = {} } = /** @type {{ method: string, correlationId?: string, params?: Record }} */ ( - event.data - ); - - await handleRequest( - method, - /** @type {Record} */ (params), - correlationId, - targetOrigin, - ); - }); -} - -bootstrap(); diff --git a/vendor/ows-signer/src/rpc.js b/vendor/ows-signer/src/rpc.js deleted file mode 100644 index bfa641c..0000000 --- a/vendor/ows-signer/src/rpc.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @typedef {(event: string, correlationId: string | undefined, data: Record) => void} EmitFn - */ - -/** - * @param {Window} target - * @param {string} targetOrigin - * @param {string} event - * @param {string | undefined} correlationId - * @param {Record} data - */ -export function emitEvent(target, targetOrigin, event, correlationId, data) { - target.postMessage( - { - v: 1, - kind: "event", - event, - correlationId, - data, - }, - targetOrigin, - ); -} - -/** - * @param {MessageEvent} event - * @returns {boolean} - */ -export function isValidParentMessage(event) { - if (event.source !== window.parent) return false; - if (window.parent === window.top) return false; - if (event.source === window) return false; - return true; -} - -/** - * @param {unknown} data - * @returns {data is { v: number, kind: 'request', method: string, correlationId?: string, params?: Record }} - */ -export function parseRequest(data) { - if (!data || typeof data !== "object") return false; - const msg = /** @type {Record} */ (data); - return ( - msg.v === 1 && - msg.kind === "request" && - typeof msg.method === "string" - ); -} diff --git a/vendor/ows-signer/src/state.js b/vendor/ows-signer/src/state.js deleted file mode 100644 index c04393c..0000000 --- a/vendor/ows-signer/src/state.js +++ /dev/null @@ -1,96 +0,0 @@ -/** @type {Uint8Array | null} */ -let recoveryPrivateKey = null; - -/** @type {Promise | null} */ -let ceremonyPromise = null; - -/** @type {string | null} */ -let trustedParentOrigin = null; - -/** - * @returns {boolean} - */ -export function isValidNesting() { - try { - return window.parent !== window && window.parent !== window.top; - } catch { - return false; - } -} - -/** - * @returns {string} - */ -export function getRpId() { - return window.location.hostname; -} - -/** - * @param {string} origin - */ -export function setTrustedParentOrigin(origin) { - if (trustedParentOrigin === null) { - trustedParentOrigin = origin; - } -} - -/** - * @returns {string | null} - */ -export function getTrustedParentOrigin() { - return trustedParentOrigin; -} - -/** - * @returns {boolean} - */ -export function hasRecoverySession() { - return recoveryPrivateKey !== null; -} - -/** - * @returns {Uint8Array | null} - */ -export function getRecoveryPrivateKey() { - return recoveryPrivateKey; -} - -/** - * @param {Uint8Array} key - */ -export function setRecoveryPrivateKey(key) { - clearRecoveryPrivateKey(); - recoveryPrivateKey = key; -} - -export function clearRecoveryPrivateKey() { - if (recoveryPrivateKey) { - recoveryPrivateKey.fill(0); - recoveryPrivateKey = null; - } -} - -/** - * @param {() => Promise} fn - * @returns {Promise} - * @template T - */ -export async function withCeremony(fn) { - if (ceremonyPromise) { - throw new Error("ceremonyInProgress"); - } - const promise = fn(); - ceremonyPromise = promise; - try { - return await promise; - } finally { - ceremonyPromise = null; - } -} - -/** - * @param {Uint8Array} bytes - */ -export function zeroize(bytes) { - if (bytes) bytes.fill(0); -} diff --git a/vendor/ows-signer/src/ui.js b/vendor/ows-signer/src/ui.js deleted file mode 100644 index 4940b72..0000000 --- a/vendor/ows-signer/src/ui.js +++ /dev/null @@ -1,68 +0,0 @@ -import { to0xHex } from "./hex.js"; - -/** @type {HTMLElement | null} */ -let root = null; - -/** - * @param {HTMLElement} container - */ -export function initUi(container) { - root = container; - root.innerHTML = ""; - root.className = "ows-signer-ui"; -} - -export function clearUi() { - if (root) root.innerHTML = ""; -} - -/** - * @param {Uint8Array} privateKey - */ -export function showPrivateKey(privateKey) { - if (!root) return; - clearUi(); - const pre = document.createElement("pre"); - pre.className = "ows-key"; - pre.textContent = to0xHex(privateKey); - const copyBtn = document.createElement("button"); - copyBtn.type = "button"; - copyBtn.className = "ows-copy"; - copyBtn.textContent = "Copy"; - copyBtn.addEventListener("click", async () => { - await navigator.clipboard.writeText(pre.textContent ?? ""); - }); - root.append(pre, copyBtn); -} - -/** - * @param {string} passwordText - * @param {string} buttonText - * @param {number} minPasswordLength - * @returns {Promise} - */ -export function promptPassphrase(passwordText, buttonText, minPasswordLength) { - if (!root) { - return Promise.reject(new Error("uiNotInitialized")); - } - clearUi(); - return new Promise((resolve, reject) => { - const input = document.createElement("input"); - input.type = "password"; - input.className = "ows-password"; - input.placeholder = passwordText; - const button = document.createElement("button"); - button.type = "button"; - button.className = "ows-confirm"; - button.textContent = buttonText; - button.addEventListener("click", () => { - if (input.value.length < minPasswordLength) { - reject(new Error("passwordTooShort")); - return; - } - resolve(input.value); - clearUi(); - }); - root.append(input, button); - }); -} diff --git a/vendor/ows-signer/src/webauthn.js b/vendor/ows-signer/src/webauthn.js deleted file mode 100644 index f01abe8..0000000 --- a/vendor/ows-signer/src/webauthn.js +++ /dev/null @@ -1,150 +0,0 @@ -import { getRpId } from "./state.js"; -import { PRF_LABEL_SECP256K1 } from "./constants.js"; -import { bufferToBase64Url } from "./hex.js"; -import { debugLog, describePrfExtensionResults } from "./debug.js"; - -/** - * @param {Uint8Array} [userId] - * @returns {Uint8Array} - */ -function randomUserId(userId) { - if (userId) return userId; - return crypto.getRandomValues(new Uint8Array(16)); -} - -/** - * @param {string} name - * @param {{ rpName?: string, userDisplayName?: string, userId?: string }} [options] - * @returns {Promise} - */ -export async function createPasskeyCredential(name, options = {}) { - const rpId = getRpId(); - const rpName = - typeof options.rpName === "string" && options.rpName - ? options.rpName - : "OWS"; - const userId = options.userId - ? Uint8Array.from(atob(options.userId), (c) => c.charCodeAt(0)) - : randomUserId(); - const displayName = options.userDisplayName ?? name; - - debugLog("createPasskeyCredential userActivation.isActive", { - isActive: navigator.userActivation?.isActive ?? false, - }); - - const credential = await navigator.credentials.create({ - publicKey: { - rp: { name: rpName, id: rpId }, - user: { - id: userId, - name, - displayName, - }, - challenge: crypto.getRandomValues(new Uint8Array(32)), - pubKeyCredParams: [{ type: "public-key", alg: -7 }], - authenticatorSelection: { - residentKey: "required", - requireResidentKey: true, - userVerification: "required", - }, - extensions: { - prf: { - enable: { - first: PRF_LABEL_SECP256K1, - }, - }, - }, - }, - }); - - if (!credential || !(credential instanceof PublicKeyCredential)) { - throw new Error("credentialCreationFailed"); - } - debugLog("createPasskeyCredential", describePrfExtensionResults(credential)); - return credential; -} - -/** - * @param {Uint8Array} [challenge] - * @param {string} [credentialId] - * @returns {Promise} - */ -export async function getPasskeyAssertion(challenge, credentialId) { - const rpId = getRpId(); - const allowCredentials = credentialId - ? [ - { - type: "public-key", - id: base64UrlToBytes(credentialId), - }, - ] - : undefined; - - const credential = await navigator.credentials.get({ - publicKey: { - challenge: challenge ?? crypto.getRandomValues(new Uint8Array(32)), - rpId, - userVerification: "required", - allowCredentials, - extensions: { - prf: { - eval: { - first: PRF_LABEL_SECP256K1, - }, - }, - }, - }, - }); - - if (!credential || !(credential instanceof PublicKeyCredential)) { - throw new Error("credentialGetFailed"); - } - debugLog("getPasskeyAssertion", describePrfExtensionResults(credential)); - return credential; -} - -/** - * @param {string} value - * @returns {Uint8Array} - */ -function base64UrlToBytes(value) { - const pad = "=".repeat((4 - (value.length % 4)) % 4); - const b64 = value.replace(/-/g, "+").replace(/_/g, "/") + pad; - const binary = atob(b64); - const out = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i); - return out; -} - -/** - * @param {PublicKeyCredential} credential - * @returns {string} - */ -export function getCredentialId(credential) { - return bufferToBase64Url(credential.rawId); -} - -/** - * @param {PublicKeyCredential} credential - * @returns {string | null} - */ -export function getPasskeyPublicKeyBase64Url(credential) { - const response = credential.response; - if ("getPublicKey" in response && typeof response.getPublicKey === "function") { - const key = response.getPublicKey(); - return key ? bufferToBase64Url(key) : null; - } - return null; -} - -/** - * @param {PublicKeyCredential} credential - * @returns {string | null} - */ -export function getAssertionSignatureBase64Url(credential) { - const response = credential.response; - if ("signature" in response && response.signature) { - return bufferToBase64Url(response.signature); - } - return null; -} diff --git a/vite.config.ts b/vite.config.ts index 2cff69a..fe847db 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,8 +7,10 @@ import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const vendorSignerRoot = path.resolve(__dirname, "vendor/ows-signer"); -const vendorSignerSrc = path.join(vendorSignerRoot, "src"); +const signerPkgSrc = path.resolve( + __dirname, + "node_modules/@1shotapi/ows-signer/src", +); const signerPublicDir = path.resolve(__dirname, "signer-static"); const MIME: Record = { @@ -77,7 +79,7 @@ function serveSignerPlugin(): Plugin { if (url.startsWith("/signer/src/")) { const filePath = resolveContainedPath( - vendorSignerSrc, + signerPkgSrc, url.slice("/signer/src/".length), ); if (!filePath || !fs.existsSync(filePath)) { From 5db4c4fd264d4beb0bb464e43af12dff39a59daf Mon Sep 17 00:00:00 2001 From: Charlie Sibbach Date: Tue, 14 Jul 2026 19:43:03 -0700 Subject: [PATCH 03/41] Create roadmap for refactor and ShadCN-ification --- roadmap.md | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 roadmap.md diff --git a/roadmap.md b/roadmap.md new file mode 100644 index 0000000..70ff100 --- /dev/null +++ b/roadmap.md @@ -0,0 +1,216 @@ +# 1Shot Wallet — ShadCN + customization roadmap + +Phased refactor of the Branding Layer UI onto ShadCN, with host-driven theming via `setStyle`, and cleaner state boundaries. Work proceeds **one phase (or one modal) per query**. + +## Goals + +1. **Integrator customization** — Host calls `proxy.rpc("setStyle", options)` before / during display; wallet applies CSS tokens + copy overrides. Re-callable for the future customization tester site. +2. **ShadCN UI** — Replace bespoke Tailwind shells/modals with tokenized shadcn/ui components. +3. **State that scales** — Theme in Context; wallet session / modal queue in Zustand; server data (later) via TanStack Query. + +## Architecture (locked) + +``` +Host OWSProxy + └── rpc("setStyle", IStyleOptions) # branding-app registerRpc + └── StyleProvider (React Context) + ├── CSS variables on document / root + └── copy / labels object + +Wallet session + modal queue → Zustand stores +Durable keys / addresses → localStorage (existing storage.ts) +Relayer / credential fetch → TanStack Query (later phase) +``` + +### UI ground rules + +- No hardcoded brand colors — use semantic tokens (`bg-primary`, `text-muted-foreground`, etc.). +- User-visible copy comes from StyleContext defaults / overrides, not scattered string literals. +- `setStyle` merges into current style state (additive, re-applicable). +- Defaults = 1Shot branding when host never calls `setStyle`. +- Style state stays separate from unlock / chain / modal runtime state. + +--- + +## Inventory — current UI surfaces + +### Shell (non-modal) + +| Surface | File | Notes | +|---------|------|--------| +| App shell | `src/App.tsx` | Loading / onboarding / main switch | +| Embedded chrome | `src/components/WalletChrome.tsx` | Title + hide | +| Onboarding | `src/components/OnboardingPanel.tsx` | Login vs create | +| Main panel | `src/components/MainPanel.tsx` | Status, addresses, chain, actions | +| Modal shell | `src/components/Modal.tsx` | Custom dialog — replace with shadcn Dialog | +| Modal router | `src/components/ModalHost.tsx` | Switches on `activeModal.kind` | +| Signer host | `src/components/SignerHost.tsx` | Keep minimal; no visual design | + +### Modals (`activeModal.kind`) + +| Kind | Component file | Purpose | +|------|----------------|---------| +| `walletSetup` | `SetupModals.tsx` → `WalletSetupModal` | Login / create / cancel | +| `passkeyName` | `SetupModals.tsx` → `PasskeyNameModal` | Name for new passkey | +| `connect` | `SetupModals.tsx` → `ConnectModal` | Account connect consent | +| `personalSign` | `SignModals.tsx` → `PersonalSignModal` | EIP-191 consent | +| `typedData` | `SignModals.tsx` → `TypedDataModal` | EIP-712 consent | +| `credentialOffer` | `CredentialModals.tsx` → `CredentialOfferModal` | OID4VCI accept | +| `credentialPresentation` | `CredentialModals.tsx` → `CredentialPresentationModal` | OID4VP disclose | +| `credentialList` | `CredentialModals.tsx` → `CredentialListModal` | Stored credentials view | +| `createBackup` | `BackupModals.tsx` → `CreateBackupModal` | Recovery create + overlay | +| `restoreBackup` | `BackupModals.tsx` → `RestoreBackupModal` | Recovery restore + overlay | + +--- + +## Phases + +### Phase 0 — Foundations (style + RPC, no visual redesign) + +**Outcome:** Host can call `setStyle`; CSS vars + copy map exist; shadcn theme CSS restored alongside wallet layout. + +- [ ] Define `IStyleOptions` (theme tokens + copy/labels + feature flags as needed). +- [ ] Defaults module (`DEFAULT_STYLE` / 1Shot branding). +- [ ] `applyStyleToDocument(options)` → set CSS variables on `document.documentElement` (or `#root`). +- [ ] `StyleProvider` + `useStyle()` context. +- [ ] Register `wallet.registerRpc("setStyle", …)` **before** `wallet.start()` (merge + apply). +- [ ] Restore / merge shadcn theme into `src/index.css` (css variables + Tailwind) without breaking `/signer/` host styles. +- [ ] Smoke: call `setStyle` from a temporary button or host console; primary/background tokens update. + +**Exit criteria:** Defaults render; RPC path works; no modal UI rewritten yet. + +--- + +### Phase 1 — Shell on ShadCN + tokens + +**Outcome:** App chrome uses Button / layout primitives; reads brand name / labels from StyleContext. + +- [ ] Wire `StyleProvider` around app in `main.tsx`. +- [ ] Refactor `WalletChrome` → tokens + styled title from copy map. +- [ ] Refactor `OnboardingPanel` → shadcn Button / typography. +- [ ] Refactor `MainPanel` → shadcn Button, Select (chain), Card or non-card sections as fit. +- [ ] Replace ad-hoc ` +
+ + + +
+ + +
+

+ + +
+ + + +
+ +
+ + + +
+

Style (setStyle RPC)

+

+ Patches are merged on the Branding Layer. Apply after the wallet connects. +

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+

+
+ + + + + + diff --git a/host/package.json b/host/package.json new file mode 100644 index 0000000..94e54ae --- /dev/null +++ b/host/package.json @@ -0,0 +1,24 @@ +{ + "name": "@1shotapi/oneshot-wallet-host", + "version": "0.0.0", + "private": true, + "description": "Local Host Layer for testing 1Shot Wallet Branding (setStyle + EIP-1193)", + "type": "module", + "scripts": { + "dev": "node scripts/dev.mjs", + "build": "tsc -p tsconfig.json --noEmit && vite build", + "clean": "node scripts/clean.mjs", + "lint": "tsc -p tsconfig.json --noEmit", + "preview": "vite preview" + }, + "dependencies": { + "@1shotapi/ows-provider": "^0.1.0", + "@1shotapi/ows-types": "^0.1.0", + "viem": "^2.55.2" + }, + "devDependencies": { + "dotenv": "^16.4.7", + "typescript": "~5.8.0", + "vite": "^7.0.4" + } +} diff --git a/host/scripts/clean.mjs b/host/scripts/clean.mjs new file mode 100644 index 0000000..512e5e6 --- /dev/null +++ b/host/scripts/clean.mjs @@ -0,0 +1,7 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const dist = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../dist"); +fs.rmSync(dist, { recursive: true, force: true }); +console.log("Removed host/dist/"); diff --git a/host/scripts/dev.mjs b/host/scripts/dev.mjs new file mode 100644 index 0000000..420266c --- /dev/null +++ b/host/scripts/dev.mjs @@ -0,0 +1,46 @@ +/** + * Start Host Layer Vite (port 5173 by default). + * Loads NGROK_DOMAIN / WALLET_IFRAME_URL from repo root .env. + */ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createServer } from "vite"; +import dotenv from "dotenv"; +import { resolveHttpsOptions, walletIframeUrl } from "../wallet-url.mjs"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, "../.."); +dotenv.config({ path: path.join(repoRoot, ".env") }); + +const preferredPort = Number(process.env.HOST_PORT ?? process.env.PORT ?? 5173); + +try { + const viteServer = await createServer({ + configFile: path.join(__dirname, "../vite.config.mjs"), + server: { + port: preferredPort, + strictPort: true, + host: "0.0.0.0", + }, + }); + await viteServer.listen(); + + const https = resolveHttpsOptions({ + certsDir: path.join(__dirname, "../certs"), + }); + const scheme = https ? "https" : "http"; + const address = viteServer.httpServer?.address(); + const port = + address && typeof address === "object" ? address.port : preferredPort; + + console.log(`1Shot Wallet test host: ${scheme}://localhost:${port}`); + console.log(` Branding Layer iframe: ${walletIframeUrl()}`); + if (scheme === "http") { + console.log( + " Tip: for HTTPS wallet (ngrok) + passkeys, serve this host over HTTPS (host/certs + HOST_HTTPS=1).", + ); + } +} catch (error) { + console.error("Failed to start host:", error); + process.exit(1); +} diff --git a/host/src/env.d.ts b/host/src/env.d.ts new file mode 100644 index 0000000..e822314 --- /dev/null +++ b/host/src/env.d.ts @@ -0,0 +1,3 @@ +/// + +declare const __WALLET_IFRAME_URL__: string; diff --git a/host/src/main.ts b/host/src/main.ts new file mode 100644 index 0000000..d29f5fb --- /dev/null +++ b/host/src/main.ts @@ -0,0 +1,382 @@ +import { OWSProxy } from "@1shotapi/ows-provider"; +import { EVMAccountAddress, EVMChainId } from "@1shotapi/ows-types"; +import { + createPublicClient, + custom, + erc20Abi, + formatUnits, + getAddress, + isAddress, + type Address, +} from "viem"; +import "./styles.css"; + +const messageInput = document.getElementById("message-input") as HTMLTextAreaElement; +const signButton = document.getElementById("sign-button") as HTMLButtonElement; +const showWalletButton = document.getElementById( + "show-wallet-button", +) as HTMLButtonElement; +const chainSelect = document.getElementById("chain-select") as HTMLSelectElement; +const chainRefreshButton = document.getElementById( + "chain-refresh-button", +) as HTMLButtonElement; +const tokenAddressInput = document.getElementById( + "token-address-input", +) as HTMLInputElement; +const checkBalanceButton = document.getElementById( + "check-balance-button", +) as HTMLButtonElement; +const tokenBalanceOutput = document.getElementById( + "token-balance-output", +) as HTMLPreElement; +const statusEl = document.getElementById("status") as HTMLParagraphElement; +const signatureOutput = document.getElementById("signature-output") as HTMLPreElement; +const walletContainer = document.getElementById("wallet-container")!; + +const styleProductName = document.getElementById( + "style-product-name", +) as HTMLInputElement; +const styleTagline = document.getElementById("style-tagline") as HTMLInputElement; +const stylePrimary = document.getElementById("style-primary") as HTMLInputElement; +const stylePrimaryFg = document.getElementById( + "style-primary-fg", +) as HTMLInputElement; +const styleBackground = document.getElementById( + "style-background", +) as HTMLInputElement; +const styleForeground = document.getElementById( + "style-foreground", +) as HTMLInputElement; +const styleRadius = document.getElementById("style-radius") as HTMLInputElement; +const styleDark = document.getElementById("style-dark") as HTMLInputElement; +const applyStyleButton = document.getElementById( + "apply-style-button", +) as HTMLButtonElement; +const presetOceanButton = document.getElementById( + "preset-ocean-button", +) as HTMLButtonElement; +const presetResetButton = document.getElementById( + "preset-reset-button", +) as HTMLButtonElement; +const styleStatusEl = document.getElementById("style-status") as HTMLParagraphElement; + +function setStatus(message: string, isError = false): void { + statusEl.textContent = message; + statusEl.classList.toggle("status--error", isError); +} + +function setStyleStatus(message: string, isError = false): void { + styleStatusEl.textContent = message; + styleStatusEl.classList.toggle("status--error", isError); +} + +/** Canonical hex chain id for `
diff --git a/src/components/OnboardingPanel.tsx b/src/components/OnboardingPanel.tsx index 0b3cc29..108e158 100644 --- a/src/components/OnboardingPanel.tsx +++ b/src/components/OnboardingPanel.tsx @@ -1,19 +1,24 @@ +import { Button } from "@/components/ui/button"; import { useWallet } from "../wallet/WalletProvider"; +import { useStyle } from "../style"; export function OnboardingPanel() { const { loginWithPasskey, createNewWalletFromUi } = useWallet(); + const { style } = useStyle(); return ( -
-

Welcome

-

- Log in with an existing passkey or create a new wallet account to get - started. +

+

+ {style.copy.productName} +

+

+ {style.copy.tagline}. Log in with an existing passkey or create a new + wallet account to get started.

- - +
); diff --git a/src/components/WalletChrome.tsx b/src/components/WalletChrome.tsx index b41c392..fd3008a 100644 --- a/src/components/WalletChrome.tsx +++ b/src/components/WalletChrome.tsx @@ -1,3 +1,5 @@ +import { XIcon } from "lucide-react"; +import { Button } from "@/components/ui/button"; import { useWallet } from "../wallet/WalletProvider"; import { useStyle } from "../style"; @@ -7,20 +9,23 @@ export function WalletChrome() { return (
-

{style.copy.productName}

- + +
); } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..8333850 --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,190 @@ +import * as React from "react" +import { Select as SelectPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react" + +function Select({ + ...props +}: React.ComponentProps) { + return +} + +function SelectGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectValue({ + ...props +}: React.ComponentProps) { + return +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps & { + size?: "sm" | "default" +}) { + return ( + + {children} + + + + + ) +} + +function SelectContent({ + className, + children, + position = "item-aligned", + align = "center", + ...props +}: React.ComponentProps) { + return ( + + + + + {children} + + + + + ) +} + +function SelectLabel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} From 7431a44dc216fa8e9f9e0f28dbe06c5abcd02713 Mon Sep 17 00:00:00 2001 From: Charlie Sibbach Date: Tue, 14 Jul 2026 21:57:23 -0700 Subject: [PATCH 06/41] Refactor 1Shot Wallet structure and update configurations - Changed the branding layer's base path from `/wallet/` to the root (`/`), simplifying the URL structure. - Updated the Dockerfile to copy the entire build output to the Nginx HTML directory. - Modified Nginx configuration to serve the branding layer from the root path. - Created a new `AGENTS.md` file with instructions for embedding the 1Shot Wallet. - Adjusted various documentation files to reflect the new URL structure and usage instructions. - Enhanced the development scripts and environment configurations for improved local testing. --- .../skills/oneshot-embedded-wallet/SKILL.md | 8 ++++---- .env.example | 2 +- AGENTS.md | 16 ++++++++++++++++ Dockerfile | 6 +++--- README.md | 19 ++++++++++--------- host/README.md | 6 +++--- host/wallet-url.mjs | 4 ++-- nginx.conf | 14 +++----------- scripts/dev.mjs | 4 ++-- skills/oneshot-embedded-wallet/SKILL.md | 8 ++++---- src/wallet/WalletProvider.tsx | 2 +- vite.config.ts | 4 ++-- 12 files changed, 51 insertions(+), 42 deletions(-) create mode 100644 AGENTS.md diff --git a/.agents/skills/oneshot-embedded-wallet/SKILL.md b/.agents/skills/oneshot-embedded-wallet/SKILL.md index 62017cd..ed9b383 100644 --- a/.agents/skills/oneshot-embedded-wallet/SKILL.md +++ b/.agents/skills/oneshot-embedded-wallet/SKILL.md @@ -17,7 +17,7 @@ Teach an agent how to embed the **1Shot Wallet** Branding Layer from a Host Laye ``` Host (your dapp) @1shotapi/ows-provider → OWSProxy - └── Branding iframe https://wallet.1shotapi.com/wallet/ + └── Branding iframe https://wallet.1shotapi.com/ └── Signing https://wallet.1shotapi.com/signer/ (same origin) ``` @@ -32,7 +32,7 @@ npm install @1shotapi/ows-provider @1shotapi/ows-types ```typescript import { OWSProxy } from "@1shotapi/ows-provider"; -const WALLET_URL = "https://wallet.1shotapi.com/wallet/"; +const WALLET_URL = "https://wallet.1shotapi.com/"; const container = document.getElementById("wallet-container")!; const proxy = await OWSProxy.create(container, WALLET_URL); @@ -52,8 +52,8 @@ const accounts = await proxy.ethereum.request({ method: "eth_requestAccounts" }) ### Local / HTTPS notes - Passkeys require a **secure-context ancestor chain**. The Host page must be HTTPS (or `localhost`) when the wallet iframe is HTTPS. -- Dev wallet URL: your ngrok or local Vite origin + `/wallet/` (e.g. `https://….ngrok-free.app/wallet/`). -- Production wallet URL: **`https://wallet.1shotapi.com/wallet/`** (Signing Layer at `/signer/` on the same origin — do not embed `/signer/` from the host). +- Dev wallet URL: your ngrok or local Vite origin root (e.g. `https://….ngrok-free.app/`). +- Production wallet URL: **`https://wallet.1shotapi.com/`** (Signing Layer at `/signer/` on the same origin — do not embed `/signer/` from the host). ## Custom RPC — `setStyle` diff --git a/.env.example b/.env.example index 636265b..e2aca9f 100644 --- a/.env.example +++ b/.env.example @@ -13,7 +13,7 @@ NGROK_AUTHTOKEN= # Test Host Layer (`npm run dev:host`, default port 5173) # HOST_PORT=5173 # Explicit Branding iframe URL (overrides NGROK_DOMAIN / localhost) -# WALLET_IFRAME_URL=https://your-name.ngrok-free.app/wallet/ +# WALLET_IFRAME_URL=https://your-name.ngrok-free.app/ # Optional HTTPS for the test host (passkeys when wallet is on HTTPS/ngrok) # HOST_HTTPS=1 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5bbba1d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ +# Agent instructions — 1Shot Embedded Wallet + +Branding Layer for the 1Shot Wallet (`wallet.1shotapi.com`). Host → Branding → Signing; never embed `/signer/` from the Host. + +## Compatibility policy (until further notice) + +Prefer **clean code over backwards compatibility**. Do not add legacy redirects, `@deprecated` aliases, dual APIs, compatibility shims, or parallel code paths “just in case.” When a pattern is renamed or superseded, update all call sites in this repo (and docs/skills) to the new shape — breaking changes are expected and welcome during active development. + +## Layout + +| Path | Content | +|------|---------| +| `/` | Branding Layer (React SPA) | +| `/signer/` | Signing Layer (`@1shotapi/ows-signer`) | + +Test Host Layer: `host/` (`npm run dev:host`). Style via Host RPC `setStyle`, not in-wallet debug knobs. diff --git a/Dockerfile b/Dockerfile index 861f1c3..b2f1ac9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ FROM node:22-alpine AS build WORKDIR /app COPY package.json package-lock.json ./ +COPY host/package.json ./host/ RUN npm ci COPY index.html vite.config.ts tsconfig.json tsconfig.node.json components.json ./ @@ -17,10 +18,9 @@ RUN npm run build FROM nginx:1.27-alpine COPY nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=build /app/dist/wallet /usr/share/nginx/html/wallet -COPY --from=build /app/dist/signer /usr/share/nginx/html/signer +COPY --from=build /app/dist/ /usr/share/nginx/html/ EXPOSE 80 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget -qO- http://127.0.0.1/wallet/ >/dev/null || exit 1 + CMD wget -qO- http://127.0.0.1/ >/dev/null || exit 1 diff --git a/README.md b/README.md index 411cb0a..a406460 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This repo is the **OWS Branding Layer** for the 1Shot Wallet — a frontend-only ``` Host Layer (integrator dapp) - └── This app /wallet/ Branding Layer (@1shotapi/ows-wallet-utils) + └── This app / Branding Layer (@1shotapi/ows-wallet-utils) └── /signer/ Signing Layer (@1shotapi/ows-signer) ``` @@ -14,7 +14,7 @@ Host Layer (integrator dapp) | Path | Content | |------|---------| -| `/wallet/` | React Branding Layer (Vite bundle) | +| `/` | React Branding Layer (Vite bundle) | | `/signer/` | Static `@1shotapi/ows-signer` ES modules | Production deliverable: a static **nginx** Docker image (no server-side runtime). @@ -36,17 +36,17 @@ npm run dev:host # Test Host Layer (setStyle knobs + EIP-1193) | Service | Local URL | |---------|-----------| -| Branding (`/wallet/`) | http://localhost:5174/wallet/ | +| Branding (`/`) | http://localhost:5174/ | | Signing (`/signer/`) | http://localhost:5174/signer/ | | Test host | http://localhost:5173 | -Passkeys need HTTPS — use the printed ngrok `/wallet/` URL as the host iframe source (`NGROK_DOMAIN` in `.env` is picked up by `dev:host`). +Passkeys need HTTPS — use the printed ngrok wallet URL as the host iframe source (`NGROK_DOMAIN` in `.env` is picked up by `dev:host`). Style testing: use the **Style (setStyle RPC)** panel on the test host (`host/`), not in-wallet debug UI. See [host/README.md](host/README.md). ## Host integration -Production iframe URL: **`https://wallet.1shotapi.com/wallet/`** +Production iframe URL: **`https://wallet.1shotapi.com/`** ```bash npm install @1shotapi/ows-provider @@ -55,7 +55,7 @@ npm install @1shotapi/ows-provider ```typescript import { OWSProxy } from "@1shotapi/ows-provider"; -const proxy = await OWSProxy.create(container, "https://wallet.1shotapi.com/wallet/"); +const proxy = await OWSProxy.create(container, "https://wallet.1shotapi.com/"); await proxy.rpc("setStyle", { copy: { productName: "Acme Wallet", tagline: "Powered by 1Shot" }, @@ -84,7 +84,7 @@ Source: [skills/oneshot-embedded-wallet](skills/oneshot-embedded-wallet/). ## Build ```bash -npm run build # dist/wallet + dist/signer +npm run build # dist/ (branding) + dist/signer npm run preview # preview production wallet build ``` @@ -93,7 +93,7 @@ npm run preview # preview production wallet build ```bash docker build -t oneshot-wallet . docker run --rm -p 8080:80 oneshot-wallet -# open http://localhost:8080/wallet/ +# open http://localhost:8080/ # signer at http://localhost:8080/signer/ ``` @@ -102,6 +102,7 @@ docker run --rm -p 8080:80 oneshot-wallet | Script | Purpose | |--------|---------| | `dev` / `dev:local` | Dev server (± ngrok) | +| `dev:host` | Test Host Layer | | `build` | Typecheck + Vite build + copy signer from `node_modules` | | `clean` | Remove `dist/` | | `lint` | `tsc --noEmit` | @@ -112,4 +113,4 @@ See [roadmap.md](roadmap.md) (ShadCN + customization phases). ## Status -Functional Branding Layer (passkey unlock, EIP-1193, signing consent, recovery, credentials) with Phase 0 style foundations (`setStyle` + StyleProvider). ShadCN UI migration in progress. +Functional Branding Layer (passkey unlock, EIP-1193, signing consent, recovery, credentials) with Phase 0–1 style foundations (`setStyle` + StyleProvider + shell). ShadCN UI migration in progress. diff --git a/host/README.md b/host/README.md index 096388c..1f1be8c 100644 --- a/host/README.md +++ b/host/README.md @@ -1,6 +1,6 @@ # Test Host Layer -Local Host Layer for exercising Host ↔ Branding communication against this repo’s wallet (`/wallet/`). +Local Host Layer for exercising Host ↔ Branding communication against this repo’s wallet (served at domain root). Adapted from OWS [`examples/host`](https://github.com/1Shot-API/open-wallet/tree/main/examples/host), plus a **Style** panel that calls `proxy.rpc("setStyle", options)`. @@ -19,8 +19,8 @@ npm run dev:host Open the printed host URL (default `http://localhost:5173`). The iframe URL comes from: 1. `WALLET_IFRAME_URL` if set -2. else `https://{NGROK_DOMAIN}/wallet/` when `NGROK_DOMAIN` is set in root `.env` -3. else `http://localhost:5174/wallet/` +2. else `https://{NGROK_DOMAIN}/` when `NGROK_DOMAIN` is set in root `.env` +3. else `http://localhost:5174/` ## HTTPS (passkeys) diff --git a/host/wallet-url.mjs b/host/wallet-url.mjs index 671faf1..3c269bc 100644 --- a/host/wallet-url.mjs +++ b/host/wallet-url.mjs @@ -26,10 +26,10 @@ export function walletIframeUrl() { } const domain = normalizeNgrokDomain(process.env.NGROK_DOMAIN); if (domain) { - return `https://${domain}/wallet/`; + return `https://${domain}/`; } const walletPort = process.env.WALLET_PORT?.trim() || "5174"; - return `http://localhost:${walletPort}/wallet/`; + return `http://localhost:${walletPort}/`; } /** diff --git a/nginx.conf b/nginx.conf index c9da7f4..56e4e73 100644 --- a/nginx.conf +++ b/nginx.conf @@ -15,16 +15,8 @@ server { return 301 /signer/; } - # Branding Layer — Vite SPA under /wallet/ - location /wallet/ { - try_files $uri $uri/ /wallet/index.html; - } - - location = /wallet { - return 301 /wallet/; - } - - location = / { - return 302 /wallet/; + # Branding Layer SPA at domain root (/signer/ above takes precedence) + location / { + try_files $uri $uri/ /index.html; } } diff --git a/scripts/dev.mjs b/scripts/dev.mjs index 6218412..5f7aa37 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -69,7 +69,7 @@ function normalizeNgrokDomain(value) { } function printUrls(tunnelUrl) { - const localWallet = `http://localhost:${listenPort}/wallet/`; + const localWallet = `http://localhost:${listenPort}/`; const localSigner = `http://localhost:${listenPort}/signer/`; console.log(`1Shot Wallet dev server: http://localhost:${listenPort}`); @@ -77,7 +77,7 @@ function printUrls(tunnelUrl) { console.log(` Signing Layer (local): ${localSigner}`); if (tunnelUrl) { - const walletUrl = new URL("/wallet/", tunnelUrl).href; + const walletUrl = new URL("/", tunnelUrl).href; const signerUrl = new URL("/signer/", tunnelUrl).href; console.log(` Branding Layer (ngrok): ${walletUrl}`); console.log(` Signing Layer (ngrok): ${signerUrl}`); diff --git a/skills/oneshot-embedded-wallet/SKILL.md b/skills/oneshot-embedded-wallet/SKILL.md index 62017cd..ed9b383 100644 --- a/skills/oneshot-embedded-wallet/SKILL.md +++ b/skills/oneshot-embedded-wallet/SKILL.md @@ -17,7 +17,7 @@ Teach an agent how to embed the **1Shot Wallet** Branding Layer from a Host Laye ``` Host (your dapp) @1shotapi/ows-provider → OWSProxy - └── Branding iframe https://wallet.1shotapi.com/wallet/ + └── Branding iframe https://wallet.1shotapi.com/ └── Signing https://wallet.1shotapi.com/signer/ (same origin) ``` @@ -32,7 +32,7 @@ npm install @1shotapi/ows-provider @1shotapi/ows-types ```typescript import { OWSProxy } from "@1shotapi/ows-provider"; -const WALLET_URL = "https://wallet.1shotapi.com/wallet/"; +const WALLET_URL = "https://wallet.1shotapi.com/"; const container = document.getElementById("wallet-container")!; const proxy = await OWSProxy.create(container, WALLET_URL); @@ -52,8 +52,8 @@ const accounts = await proxy.ethereum.request({ method: "eth_requestAccounts" }) ### Local / HTTPS notes - Passkeys require a **secure-context ancestor chain**. The Host page must be HTTPS (or `localhost`) when the wallet iframe is HTTPS. -- Dev wallet URL: your ngrok or local Vite origin + `/wallet/` (e.g. `https://….ngrok-free.app/wallet/`). -- Production wallet URL: **`https://wallet.1shotapi.com/wallet/`** (Signing Layer at `/signer/` on the same origin — do not embed `/signer/` from the host). +- Dev wallet URL: your ngrok or local Vite origin root (e.g. `https://….ngrok-free.app/`). +- Production wallet URL: **`https://wallet.1shotapi.com/`** (Signing Layer at `/signer/` on the same origin — do not embed `/signer/` from the host). ## Custom RPC — `setStyle` diff --git a/src/wallet/WalletProvider.tsx b/src/wallet/WalletProvider.tsx index 27399fc..6ef7382 100644 --- a/src/wallet/WalletProvider.tsx +++ b/src/wallet/WalletProvider.tsx @@ -614,7 +614,7 @@ export function WalletProvider({ children }: { children: ReactNode }) { setBootError(error instanceof Error ? error.message : String(error)); }); - // Paint UI without awaiting host handshake — standalone /wallet/ has no parent. + // Paint UI without awaiting host handshake — standalone branding has no parent. const listed = await credentialRepository.list(); if (cancelled) return; setCredentialCount(listed.length); diff --git a/vite.config.ts b/vite.config.ts index fe847db..89c1da6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -106,7 +106,7 @@ function serveSignerPlugin(): Plugin { } export default defineConfig({ - base: "/wallet/", + base: "/", publicDir: false, plugins: [react(), tailwindcss(), serveSignerPlugin()], resolve: { @@ -126,7 +126,7 @@ export default defineConfig({ host: "0.0.0.0", }, build: { - outDir: "dist/wallet", + outDir: "dist", emptyOutDir: true, sourcemap: true, }, From 318fb6695a5c918110cf34ef894471475354bfd5 Mon Sep 17 00:00:00 2001 From: Charlie Sibbach Date: Tue, 14 Jul 2026 22:43:56 -0700 Subject: [PATCH 07/41] Refactor modals and UI components for improved dialog handling - Replaced the existing modal implementation with a new Dialog component using shadcn Dialog for better accessibility and functionality. - Updated the MainPanel to conditionally render buttons based on backup status. - Introduced new Input, Label, and Textarea components for consistent UI styling. - Enhanced BackupModals to wait for signer slots before proceeding with backup and restore actions. - Improved SetupModals to utilize the new Input and Label components for better user experience. - Added new dialog-related components (Dialog, DialogContent, DialogHeader, etc.) to streamline modal usage across the application. --- roadmap.md | 8 +- src/components/MainPanel.tsx | 10 +- src/components/Modal.tsx | 106 ++++++++-------- src/components/modals/BackupModals.tsx | 63 ++++++++-- src/components/modals/SetupModals.tsx | 51 ++++---- src/components/ui/dialog.tsx | 168 +++++++++++++++++++++++++ src/components/ui/input.tsx | 19 +++ src/components/ui/label.tsx | 22 ++++ src/components/ui/textarea.tsx | 18 +++ 9 files changed, 370 insertions(+), 95 deletions(-) create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/textarea.tsx diff --git a/roadmap.md b/roadmap.md index 9cbe248..3cd7770 100644 --- a/roadmap.md +++ b/roadmap.md @@ -102,10 +102,10 @@ Relayer / credential fetch → TanStack Query (later phase) **Outcome:** One shadcn Dialog-based `Modal` / `AppDialog` used by all kinds; ModalHost unchanged in routing. -- [ ] `npx shadcn add dialog` (and Input / Label / Textarea as needed). -- [ ] Replace `src/components/Modal.tsx` with shadcn Dialog wrapper (actions, title, description slots). -- [ ] Keep `ModalHost` switch; each modal migrates in later phases. -- [ ] Accessibility: focus trap, Escape → reject/cancel where appropriate. +- [x] `npx shadcn add dialog` (and Input / Label / Textarea as needed). +- [x] Replace `src/components/Modal.tsx` with shadcn Dialog wrapper (actions, title, description slots). +- [x] Keep `ModalHost` switch; each modal migrates in later phases. +- [x] Accessibility: focus trap, Escape → reject/cancel where appropriate. **Exit criteria:** At least one modal (prefer `connect`) uses the new Dialog shell end-to-end. diff --git a/src/components/MainPanel.tsx b/src/components/MainPanel.tsx index c80cb64..fce98f2 100644 --- a/src/components/MainPanel.tsx +++ b/src/components/MainPanel.tsx @@ -6,6 +6,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { hasBackup } from "../storage"; import { useWallet } from "../wallet/WalletProvider"; import { useStyle } from "../style"; @@ -24,6 +25,7 @@ export function MainPanel() { openCredentialList, } = useWallet(); const { style } = useStyle(); + const canRestore = hasBackup(); const status = unlocked ? "Unlocked" @@ -87,7 +89,11 @@ export function MainPanel() {
- {unlocked ? ( + {/* + Create when unlocked, or when locked with no stored backup (openCreateBackup + → ensureReady will unlock / run setup). Restore only when locked + backup. + */} + {unlocked || !canRestore ? ( + ))} -
+ ) : null} -
- + + ); } diff --git a/src/components/modals/BackupModals.tsx b/src/components/modals/BackupModals.tsx index 27e6613..bba4e9f 100644 --- a/src/components/modals/BackupModals.tsx +++ b/src/components/modals/BackupModals.tsx @@ -14,6 +14,33 @@ function waitForPaint(): Promise { }); } +/** + * Dialog portals content; refs on the footer slot are not always set on the + * first effect tick. Wait a few frames before treating them as missing. + */ +async function waitForSignerSlot( + getHome: () => HTMLElement | null, + getSlot: () => HTMLElement | null, + isAborted: () => boolean, +): Promise<{ home: HTMLElement; slot: HTMLElement } | null> { + for (let attempt = 0; attempt < 60; attempt++) { + if (isAborted()) return null; + const home = getHome(); + const slot = getSlot(); + if (home && slot) { + return { home, slot }; + } + await waitForPaint(); + } + if (isAborted()) return null; + const home = getHome(); + const slot = getSlot(); + if (home && slot) { + return { home, slot }; + } + return null; +} + function formatBackupError(error: unknown): string { if (error instanceof Error) { const message = error.message; @@ -47,15 +74,21 @@ export function CreateBackupModal({ useEffect(() => { abortedRef.current = false; - const home = signerContainerRef.current; - const slot = signerSlotRef.current; - if (!home || !slot) { - onReject(new Error("Signer not ready for backup")); - return; - } void (async () => { try { + const nodes = await waitForSignerSlot( + () => signerContainerRef.current, + () => signerSlotRef.current, + () => abortedRef.current, + ); + if (abortedRef.current) return; + if (!nodes) { + onReject(new Error("Signer not ready for backup")); + return; + } + const { home, slot } = nodes; + // Unlock is usually a no-op here (create only when unlocked); this still // waits for Signing Layer load before getSigner(). await ensureReady(); @@ -232,15 +265,21 @@ export function RestoreBackupModal({ useEffect(() => { abortedRef.current = false; - const home = signerContainerRef.current; - const slot = signerSlotRef.current; - if (!home || !slot) { - onReject(new Error("Signer not ready for restore")); - return; - } void (async () => { try { + const nodes = await waitForSignerSlot( + () => signerContainerRef.current, + () => signerSlotRef.current, + () => abortedRef.current, + ); + if (abortedRef.current) return; + if (!nodes) { + onReject(new Error("Signer not ready for restore")); + return; + } + const { home, slot } = nodes; + // Restore is offered while locked — wait for Signing Layer only. // Do not call ensureReady() (that would unlock / run setup first). await awaitSignerReady(); diff --git a/src/components/modals/SetupModals.tsx b/src/components/modals/SetupModals.tsx index 0fd6a34..dc95899 100644 --- a/src/components/modals/SetupModals.tsx +++ b/src/components/modals/SetupModals.tsx @@ -1,4 +1,6 @@ import { useState } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { Modal } from "../Modal"; import type { WalletSetupChoice } from "../../wallet/modalTypes"; @@ -76,30 +78,31 @@ export function PasskeyNameModal({ Choose a name for this wallet passkey. Your device will use it when you create the credential and when you sign in later.

- - { - setName(event.target.value); - setError(null); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); - submit(); - } - }} - className="mb-2 box-border w-full rounded-md border border-[color-mix(in_srgb,CanvasText_25%,transparent)] bg-[Canvas] px-2.5 py-2 text-inherit" - /> - {error ?

{error}

: null} +
+ + { + setName(event.target.value); + setError(null); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + submit(); + } + }} + /> +
+ {error ? ( +

{error}

+ ) : null} ); } diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..2b0f006 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,168 @@ +"use client" + +import * as React from "react" +import { Dialog as DialogPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { XIcon } from "lucide-react" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean +}) { + return ( +
+ {children} + {showCloseButton && ( + + + + )} +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..d763cd9 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,19 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..f752f82 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,22 @@ +import * as React from "react" +import { Label as LabelPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label } diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx new file mode 100644 index 0000000..04d27f7 --- /dev/null +++ b/src/components/ui/textarea.tsx @@ -0,0 +1,18 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { + return ( + -
- - -
-

- - -
- - - -
- -
- - - -
-

Style (setStyle RPC)

-

- Patches are merged on the Branding Layer. Apply after the wallet connects. -

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - -
-

-
- - - - - + + + + + + + + + 1Shot Wallet — Host Playground + + +
+ + diff --git a/host/package.json b/host/package.json index 94e54ae..bee0d81 100644 --- a/host/package.json +++ b/host/package.json @@ -14,10 +14,26 @@ "dependencies": { "@1shotapi/ows-provider": "^0.1.0", "@1shotapi/ows-types": "^0.1.0", + "@fontsource-variable/geist": "^5.2.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.24.0", + "radix-ui": "^1.6.2", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "shadcn": "^4.13.0", + "tailwind-merge": "^3.6.0", "viem": "^2.55.2" }, "devDependencies": { + "@tailwindcss/vite": "^4.3.2", + "@types/node": "^22.20.1", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^4.7.0", "dotenv": "^16.4.7", + "tailwindcss": "^4.3.2", + "tw-animate-css": "^1.4.0", "typescript": "~5.8.0", "vite": "^7.0.4" } diff --git a/host/public/1Shot-Icon-New.svg b/host/public/1Shot-Icon-New.svg new file mode 100644 index 0000000..d577373 --- /dev/null +++ b/host/public/1Shot-Icon-New.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/host/public/favicon.svg b/host/public/favicon.svg new file mode 100644 index 0000000..cd4a6f0 --- /dev/null +++ b/host/public/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/host/src/App.tsx b/host/src/App.tsx new file mode 100644 index 0000000..7cf478d --- /dev/null +++ b/host/src/App.tsx @@ -0,0 +1,330 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { OWSProxy } from "@1shotapi/ows-provider"; +import { EVMAccountAddress, EVMChainId } from "@1shotapi/ows-types"; +import { + createPublicClient, + custom, + erc20Abi, + formatUnits, + getAddress, + isAddress, + type Address, +} from "viem"; +import { AppHeader } from "./components/AppHeader"; +import { AppSidebar, type HostMode } from "./components/AppSidebar"; +import { DesignPanel } from "./components/DesignPanel"; +import { TestPanel } from "./components/TestPanel"; +import { HOST_CHAINS } from "./components/WalletActions"; +import { SidebarInset, SidebarProvider } from "./components/ui/sidebar"; +import { TooltipProvider } from "./components/ui/tooltip"; + +function normalizeChainIdHex(value: string): EVMChainId { + return EVMChainId(`0x${BigInt(value).toString(16)}`); +} + +export function App() { + const walletContainerRef = useRef(null); + const proxyRef = useRef(null); + + const [mode, setMode] = useState("test"); + const [ready, setReady] = useState(false); + const [busy, setBusy] = useState(false); + const [chainId, setChainId] = useState(HOST_CHAINS[0].value); + const [message, setMessage] = useState("Hello from 1Shot Wallet"); + const [tokenAddress, setTokenAddress] = useState(""); + const [status, setStatus] = useState("Connecting to wallet…"); + const [statusIsError, setStatusIsError] = useState(false); + const [signature, setSignature] = useState(null); + const [tokenBalance, setTokenBalance] = useState(null); + + const reportStatus = useCallback((next: string, isError = false) => { + setStatus(next); + setStatusIsError(isError); + }, []); + + const refreshChainFromWallet = useCallback( + async (proxy: OWSProxy): Promise => { + const next = normalizeChainIdHex( + await proxy.ethereum.request({ method: "eth_chainId" }), + ); + setChainId(String(next)); + return next; + }, + [], + ); + + useEffect(() => { + let cancelled = false; + const container = walletContainerRef.current; + if (!container) return; + + console.info( + "[oneshot-wallet-host] embedding Branding Layer", + __WALLET_IFRAME_URL__, + ); + + void (async () => { + try { + const proxy = await OWSProxy.create(container, __WALLET_IFRAME_URL__, { + // Slightly taller flyout for design-mode preview. + walletSizeX: 360, + walletSizeY: 520, + }); + if (cancelled) return; + proxyRef.current = proxy; + setReady(true); + try { + const connectedChain = await refreshChainFromWallet(proxy); + reportStatus( + `Wallet connected on ${connectedChain}. Enter a message and click Sign.`, + ); + } catch (error) { + reportStatus( + error instanceof Error ? error.message : "Failed to read chain id", + true, + ); + } + } catch (error) { + if (cancelled) return; + console.error("[oneshot-wallet-host] failed to start", error); + reportStatus( + error instanceof Error + ? error.message + : "Failed to connect to wallet", + true, + ); + } + })(); + + return () => { + cancelled = true; + }; + }, [refreshChainFromWallet, reportStatus]); + + // Design mode keeps the branding flyout visible; Test keeps it hidden. + useEffect(() => { + const proxy = proxyRef.current; + if (!proxy || !ready) return; + if (mode === "design") { + proxy.showWallet(); + } else { + proxy.hideWallet(); + } + }, [mode, ready]); + + const resolveAccount = async ( + proxy: OWSProxy, + ): Promise => { + let accounts = await proxy.ethereum.request({ method: "eth_accounts" }); + if (accounts.length === 0) { + accounts = await proxy.ethereum.request({ + method: "eth_requestAccounts", + }); + } + const account = accounts[0]; + if (!account) { + throw new Error("No account returned from wallet"); + } + return account; + }; + + const handleChainChange = (next: string) => { + const proxy = proxyRef.current; + if (!proxy) return; + const selected = EVMChainId(next as `0x${string}`); + setBusy(true); + void (async () => { + try { + await proxy.ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: selected }], + }); + setChainId(String(selected)); + reportStatus(`Switched to ${selected}`); + } catch (error) { + await refreshChainFromWallet(proxy).catch(() => undefined); + reportStatus( + error instanceof Error ? error.message : "Chain switch failed", + true, + ); + } finally { + setBusy(false); + } + })(); + }; + + const handleRefreshChain = () => { + const proxy = proxyRef.current; + if (!proxy) return; + setBusy(true); + void (async () => { + try { + const next = await refreshChainFromWallet(proxy); + reportStatus(`Chain synced: ${next}`); + } catch (error) { + reportStatus( + error instanceof Error ? error.message : "Failed to refresh chain", + true, + ); + } finally { + setBusy(false); + } + })(); + }; + + const handleSign = () => { + const proxy = proxyRef.current; + if (!proxy) return; + const trimmed = message.trim(); + if (!trimmed) { + reportStatus("Enter a message to sign.", true); + return; + } + + setBusy(true); + setSignature(null); + reportStatus("Requesting accounts…"); + + void (async () => { + try { + const account = await resolveAccount(proxy); + reportStatus("Approve the passkey prompt to sign…"); + const nextSignature = await proxy.ethereum.request({ + method: "personal_sign", + params: [trimmed, account], + }); + setSignature(nextSignature); + reportStatus(`Signed as ${account}`); + } catch (error) { + reportStatus( + error instanceof Error ? error.message : "Signing failed", + true, + ); + } finally { + setBusy(false); + } + })(); + }; + + const handleCheckBalance = () => { + const proxy = proxyRef.current; + if (!proxy) return; + const rawAddress = tokenAddress.trim(); + if (!isAddress(rawAddress)) { + reportStatus("Enter a valid ERC-20 contract address.", true); + setTokenBalance(null); + return; + } + + setBusy(true); + setTokenBalance(null); + reportStatus("Reading token balance…"); + + void (async () => { + try { + const token = getAddress(rawAddress) as Address; + const account = await resolveAccount(proxy); + const owner = getAddress(account) as Address; + const client = createPublicClient({ + transport: custom(proxy.ethereum), + }); + + const [name, symbol, balance, decimals] = await Promise.all([ + client.readContract({ + address: token, + abi: erc20Abi, + functionName: "name", + }), + client.readContract({ + address: token, + abi: erc20Abi, + functionName: "symbol", + }), + client.readContract({ + address: token, + abi: erc20Abi, + functionName: "balanceOf", + args: [owner], + }), + client.readContract({ + address: token, + abi: erc20Abi, + functionName: "decimals", + }), + ]); + + setTokenBalance( + [ + `Contract: ${token}`, + `Account: ${owner}`, + `Name: ${name}`, + `Symbol: ${symbol}`, + `Balance: ${formatUnits(balance, decimals)} ${symbol}`, + `Raw: ${balance.toString()}`, + ].join("\n"), + ); + reportStatus( + `Balance for ${symbol}: ${formatUnits(balance, decimals)}`, + ); + } catch (error) { + reportStatus( + error instanceof Error + ? error.message + : "Failed to read token balance", + true, + ); + } finally { + setBusy(false); + } + })(); + }; + + const handleShowWallet = () => { + proxyRef.current?.showWallet(); + reportStatus("Wallet panel shown. Use × in the wallet to hide."); + }; + + const handleApplyStyle = async (options: Record) => { + const proxy = proxyRef.current; + if (!proxy) { + throw new Error("Wallet not connected"); + } + await proxy.rpc("setStyle", options); + }; + + const walletActionProps = { + ready, + busy, + chainId, + message, + tokenAddress, + status, + statusIsError, + signature, + tokenBalance, + onChainChange: handleChainChange, + onRefreshChain: handleRefreshChain, + onMessageChange: setMessage, + onTokenAddressChange: setTokenAddress, + onSign: handleSign, + onShowWallet: handleShowWallet, + onCheckBalance: handleCheckBalance, + }; + + return ( + + + + + + {mode === "test" ? ( + + ) : ( + + )} + +