diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..febcbe4 --- /dev/null +++ b/.env.example @@ -0,0 +1,45 @@ +# Browser Hand — Clerk +# Copy to .env at the repo root: cp .env.example .env +# Never commit .env. Rebuild the extension after changing any WXT_ value +# (`npm run build:extension`); those are inlined at build time. +# +# WXT only exposes variables prefixed with WXT_ to the extension bundle. +# CLERK_SECRET_KEY has no WXT_ prefix, so it stays in the Node brand script. + +# Public. Clerk Dashboard → API keys → Chrome extension → Publishable key. +WXT_CLERK_PUBLISHABLE_KEY=pk_test_replace_me + +# Frontend API URL from the same API keys page. +# Optional. Added as an explicit host permission when set. +WXT_CLERK_FRONTEND_API=https://your-subdomain.clerk.accounts.dev + +# Optional. Set only when a website should share this Clerk session with the extension. +# Development example: http://localhost:3000 +# Production example: https://app.example.com +WXT_CLERK_SYNC_HOST= + +# Optional. Pins the unpacked extension id (manifest "key"). Public key only. +# Never put the Chrome Web Store private key here. +WXT_EXTENSION_PUBLIC_KEY= + +# Secret. Server-side only. Used by `npm run clerk:brand` to set allowed origins. +CLERK_SECRET_KEY=sk_test_replace_me + +# Extension id from chrome://extensions (32 characters, a-p), without the chrome-extension:// prefix. +CLERK_EXTENSION_ID= + +# Optional extra origins to keep when the brand script rewrites allowed_origins. +# CLERK_EXTRA_ALLOWED_ORIGINS=https://app.example.com + +# Optional Platform API credentials. When CLERK_APPLICATION_ID is set, `npm run clerk:brand` +# renames the Clerk application to "Browser Hand" and uploads the glove logo. +# The Platform API may require its own token and may need to be enabled for the account. +# CLERK_PLATFORM_SECRET_KEY= +# CLERK_APPLICATION_ID=app_replace_me + +# Optional path to a PNG used as the Clerk application logo. Defaults to the glove mark. +# CLERK_LOGO_PATH=docs/brand/derived/mark-transparent-512.png + +# Optional. When set, the account page loads orders from this URL instead of sample data. +# See README "Order history". +# WXT_ORDER_HISTORY_URL=https://orders.example.com/v1/orders diff --git a/.gitignore b/.gitignore index 83aea73..6f8e75d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ _scratch/ .env .env.*.local .env.local +.env.development +.env.production +.env.chrome +.env.*.chrome .idea/ .output/ .vscode/ diff --git a/README.md b/README.md index 4b55cc4..75f468e 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,117 @@ npm run smoke:live Coming from upstream or an older AutoHub-era install? Start with **[MIGRATING.md](./MIGRATING.md)**. +## Account sign-in (Clerk) + +The toolbar popup still controls the local relay with or without an account. Sign-in, sign-up, sign-out, and order history live on an extension account page (`account.html`), opened from **Sign in** / **Account** in the popup. Sessions are handled by [Clerk’s Chrome extension SDK](https://clerk.com/docs/chrome-extension/getting-started/quickstart) (`@clerk/chrome-extension`). Components use Browser Hand colors (ink `#0B0A12`, bone, cyan `#7EE7FF`) and the glove mark. + +There is no Clerk application or key in this repo. Create one, then add keys locally. **Never commit `.env`.** + +### 1. Create the Clerk application + +1. In the [Clerk Dashboard](https://dashboard.clerk.com), create an application named **Browser Hand**. +2. Enable **Email** (password and/or email code). OAuth can be added later; extension pages are a poor place for provider redirects until the extension origin is allowed. +3. Open **Native applications** and enable the **Native API**. Chrome extensions need it. +4. On **API keys**, choose the Chrome extension quick copy. You need the **publishable key** and the **Frontend API URL**. + +### 2. Put keys in `.env` + +```bash +cp .env.example .env +``` + +| Variable | Where it goes | Notes | +| --------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `WXT_CLERK_PUBLISHABLE_KEY` | Extension bundle (public) | `pk_test_…` or `pk_live_…`. WXT inlines `WXT_` variables at build time. | +| `CLERK_SECRET_KEY` | `npm run clerk:brand` only | `sk_test_…` / `sk_live_…`. No `WXT_` prefix, so it is not bundled. | +| `WXT_CLERK_FRONTEND_API` | Optional host permission | `https://.clerk.accounts.dev` from the same API keys page. | +| `WXT_CLERK_SYNC_HOST` | Optional | Set only when a website should share this session. Development is often `http://localhost:3000`. Leave empty for extension-only sign-in. | +| `CLERK_EXTENSION_ID` | Brand script | The 32-character id on `chrome://extensions` (letters a–p). | +| `WXT_EXTENSION_PUBLIC_KEY` | Optional manifest `key` | Public key that pins the unpacked extension id. Not the Web Store private key. | +| `CLERK_APPLICATION_ID` | Optional platform branding | `app_…`. With a platform token, the brand script sets the application name and logo. | +| `CLERK_PLATFORM_SECRET_KEY` | Optional | Platform API token when it is not the instance secret. Platform API access is separate and may be gated. | +| `WXT_ORDER_HISTORY_URL` | Optional order API | See below. | + +Rebuild and reload the unpacked extension after any `WXT_` change. Vite bakes those values into the build; editing `.env` alone does not update a loaded extension. + +```bash +npm run build:extension +# Chrome → chrome://extensions → Reload +# Popup → Sign in → extension page account.html +``` + +### 3. Allow the `chrome-extension://` origin + +Clerk rejects Frontend API calls from an extension until that origin is on the instance. The id is stable for a given unpacked path, and it changes if you set `WXT_EXTENSION_PUBLIC_KEY` or publish to the Chrome Web Store. + +```bash +# .env +CLERK_SECRET_KEY=sk_test_… +CLERK_EXTENSION_ID=abcdefghijklmnopabcdefghijklmnop # from chrome://extensions, letters a-p only +npm run clerk:brand +``` + +That uses `@clerk/backend` `instance.update` (`PATCH /v1/instance`) and **merges** `chrome-extension://` into `allowed_origins` so other origins are kept. `CLERK_EXTRA_ALLOWED_ORIGINS` (comma-separated) is merged too. `--dry-run` prints the plan and does not call Clerk. + +`npm run clerk:brand` also calls Clerk’s Platform API when `CLERK_APPLICATION_ID` is set: + +- `PATCH /v1/platform/applications/{id}` with `{ "name": "Browser Hand" }` +- `POST /v1/platform/applications/{id}/logo` with the transparent glove (`docs/brand/derived/mark-transparent-512.png`) +- the same image as the favicon when Clerk accepts it + +If the Platform API returns 401 or 403, set the name and logo in the Dashboard under **Settings → Branding** instead. Emails and the hosted Account Portal use that name and logo. The extension components use the `appearance` prop either way. + +Sync host (only if a website shares the session): set `WXT_CLERK_SYNC_HOST` to that site’s origin, add the extension id under Clerk’s allowed origins (the brand script does this), and rebuild. The extension then requests the `cookies` permission, which Clerk needs for sync-host mode. Extension-only sign-in leaves that permission off and does not need a sync host. + +### Order history + +There is no order database in this repo. Until `WXT_ORDER_HISTORY_URL` is set, the account page shows **sample** orders from `extension/account/orders.ts` and says so. A real service is a `GET` endpoint: + +```http +GET {WXT_ORDER_HISTORY_URL}?userId={clerkUserId}&limit=20&cursor={opaque} +Authorization: Bearer {clerkSessionToken} +Accept: application/json +``` + +```json +{ + "orders": [ + { + "id": "ord_123", + "number": "BH-1042", + "createdAt": "2026-07-18T15:04:00.000Z", + "status": "fulfilled", + "currency": "usd", + "totalAmountCents": 2900, + "lineItems": [ + { + "id": "li_1", + "name": "Browser Hand seat", + "quantity": 1, + "unitAmountCents": 2900, + "currency": "usd" + } + ] + } + ], + "nextCursor": null +} +``` + +`status` is `pending`, `paid`, `fulfilled`, `cancelled`, or `refunded`. Amounts are integer cents. Verify the bearer token with `@clerk/backend` and return only that user’s orders. Do not trust the `userId` query param by itself. + +```ts +import { verifyToken } from "@clerk/backend"; + +const claims = await verifyToken(sessionToken, { + secretKey: process.env.CLERK_SECRET_KEY, + authorizedParties: ["chrome-extension://"], +}); +const userId = claims.sub; +``` + +Set `WXT_ORDER_HISTORY_URL` to the endpoint, rebuild, and reload. Failures stay on the HTTP source; the page does not swap in sample orders after a real request fails. + ## Headless / remote-debug (optional) For CI and throwaway automation, the upstream-style CLI remains: diff --git a/extension/__tests__/CDPRouter.test.ts b/extension/__tests__/CDPRouter.test.ts index a0f1eea..3cfbe28 100644 --- a/extension/__tests__/CDPRouter.test.ts +++ b/extension/__tests__/CDPRouter.test.ts @@ -437,7 +437,7 @@ describe("CDPRouter", () => { const previousGroup = tabs.group; const previousTabGroups = (chrome as typeof chrome & { tabGroups?: unknown }).tabGroups; tabs.group = group; - (chrome as typeof chrome & { tabGroups?: unknown }).tabGroups = { get, update }; + (chrome as unknown as { tabGroups: unknown }).tabGroups = { get, update }; try { const result = await cdpRouter.handleCommand({ id: 1, @@ -482,7 +482,7 @@ describe("CDPRouter", () => { const previousGroup = tabs.group; const previousTabGroups = (chrome as typeof chrome & { tabGroups?: unknown }).tabGroups; tabs.group = group; - (chrome as typeof chrome & { tabGroups?: unknown }).tabGroups = { get, update }; + (chrome as unknown as { tabGroups: unknown }).tabGroups = { get, update }; const command = { id: 1, method: "forwardCDPCommand" as const, diff --git a/extension/__tests__/account.test.ts b/extension/__tests__/account.test.ts new file mode 100644 index 0000000..21db6c3 --- /dev/null +++ b/extension/__tests__/account.test.ts @@ -0,0 +1,166 @@ +import { describe, expect, it, vi } from "vitest"; +import { BROWSER_HAND_COLORS, browserHandAppearance } from "../account/brand"; +import { + BROWSER_HAND_APPLICATION_NAME, + ClerkPlatformError, + applicationLogoRequest, + applicationNameRequest, + chromeExtensionOrigin, + logoFormData, + mergeAllowedOrigins, + parseOriginList, + sendPlatformRequest, + syncAllowedOrigins, +} from "../account/clerk-setup"; +import { isConfiguredPublishableKey, readClerkConfig } from "../account/config"; +import { isAccountSnapshot, signedOutSnapshot, snapshotFromUser } from "../account/session"; + +describe("Clerk config", () => { + it("reads only public WXT variables and treats blanks as unset", () => { + const config = readClerkConfig({ + WXT_CLERK_PUBLISHABLE_KEY: " pk_test_abc ", + WXT_CLERK_SYNC_HOST: " ", + WXT_CLERK_FRONTEND_API: "https://example.clerk.accounts.dev", + WXT_ORDER_HISTORY_URL: "", + }); + expect(config).toEqual({ + publishableKey: "pk_test_abc", + syncHost: undefined, + frontendApi: "https://example.clerk.accounts.dev", + orderHistoryUrl: undefined, + }); + expect(config).not.toHaveProperty("secretKey"); + }); + + it("treats the example placeholder key as unconfigured", () => { + expect(isConfiguredPublishableKey(undefined)).toBe(false); + expect(isConfiguredPublishableKey("pk_test_replace_me")).toBe(false); + expect(isConfiguredPublishableKey("not-a-key")).toBe(false); + expect(isConfiguredPublishableKey("pk_live_real_key")).toBe(true); + }); +}); + +describe("account snapshot", () => { + it("mirrors display fields and omits tokens", () => { + const snapshot = snapshotFromUser( + { + id: "user_1", + fullName: "Ada Lovelace", + imageUrl: "https://img.example/a.png", + primaryEmailAddress: { emailAddress: "ada@example.com" }, + }, + new Date("2026-11-08T00:00:00.000Z") + ); + expect(snapshot).toEqual({ + signedIn: true, + userId: "user_1", + email: "ada@example.com", + name: "Ada Lovelace", + imageUrl: "https://img.example/a.png", + updatedAt: "2026-11-08T00:00:00.000Z", + }); + expect(isAccountSnapshot(snapshot)).toBe(true); + expect(isAccountSnapshot(signedOutSnapshot())).toBe(true); + expect(isAccountSnapshot({ signedIn: true })).toBe(false); + }); +}); + +describe("Browser Hand Clerk appearance", () => { + it("uses the cyan spark and the glove mark", () => { + const appearance = browserHandAppearance( + "chrome-extension://abcdefghijklmnopabcdefghijklmnop/icons/icon-128.png" + ); + expect(appearance.variables?.colorPrimary).toBe(BROWSER_HAND_COLORS.cyan); + expect(appearance.variables?.colorPrimaryForeground).toBe(BROWSER_HAND_COLORS.ink); + expect(appearance.variables?.colorForeground).toBe(BROWSER_HAND_COLORS.ink); + expect(appearance.options?.logoImageUrl).toContain("icon-128.png"); + expect(appearance.options?.logoPlacement).toBe("inside"); + }); +}); + +describe("Clerk instance origins", () => { + it("builds a chrome-extension origin and rejects anything else", () => { + expect(chromeExtensionOrigin("abcdefghijklmnopabcdefghijklmnop")).toBe( + "chrome-extension://abcdefghijklmnopabcdefghijklmnop" + ); + expect(chromeExtensionOrigin("chrome-extension://abcdefghijklmnopabcdefghijklmnop/")).toBe( + "chrome-extension://abcdefghijklmnopabcdefghijklmnop" + ); + expect(() => chromeExtensionOrigin("not-an-id")).toThrow(/CLERK_EXTENSION_ID/); + }); + + it("merges new origins without dropping existing ones", () => { + expect( + mergeAllowedOrigins( + ["https://app.example.com", "chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], + ["chrome-extension://abcdefghijklmnopabcdefghijklmnop", "https://app.example.com"] + ) + ).toEqual([ + "https://app.example.com", + "chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "chrome-extension://abcdefghijklmnopabcdefghijklmnop", + ]); + }); + + it("updates the instance with the merged list", async () => { + const update = vi.fn(async () => undefined); + const merged = await syncAllowedOrigins( + { + get: async () => ({ allowedOrigins: ["https://kept.example"] }), + update, + }, + ["chrome-extension://abcdefghijklmnopabcdefghijklmnop"] + ); + expect(merged).toEqual([ + "https://kept.example", + "chrome-extension://abcdefghijklmnopabcdefghijklmnop", + ]); + expect(update).toHaveBeenCalledWith({ allowedOrigins: merged }); + }); + + it("parses extra origins", () => { + expect( + parseOriginList(" https://a.example , chrome-extension://abcdefghijklmnopabcdefghijklmnop ") + ).toEqual(["https://a.example", "chrome-extension://abcdefghijklmnopabcdefghijklmnop"]); + }); +}); + +describe("Clerk platform branding", () => { + it("renames the application to Browser Hand", () => { + const request = applicationNameRequest("app_123", "platform-token"); + expect(request.url).toBe("https://api.clerk.com/v1/platform/applications/app_123"); + expect(request.init.method).toBe("PATCH"); + expect(request.init.body).toBe(JSON.stringify({ name: BROWSER_HAND_APPLICATION_NAME })); + expect(new Headers(request.init.headers).get("Authorization")).toBe("Bearer platform-token"); + }); + + it("uploads the logo as a file part", () => { + const form = logoFormData(new Uint8Array([1, 2, 3]), "mark.png"); + const file = form.get("file"); + expect(file).toBeInstanceOf(Blob); + expect((file as File).name).toBe("mark.png"); + const request = applicationLogoRequest( + "app_123", + "platform-token", + new Uint8Array([1]), + "mark.png" + ); + expect(request.url).toContain("/platform/applications/app_123/logo"); + expect(request.init.method).toBe("POST"); + expect(request.init.body).toBeInstanceOf(FormData); + }); + + it("surfaces platform error status without calling a fallback", async () => { + const fetchImpl = vi.fn(async () => new Response("denied", { status: 403 })); + await expect( + sendPlatformRequest( + applicationNameRequest("app_123", "token", "Browser Hand", "https://api.example"), + fetchImpl + ) + ).rejects.toMatchObject({ status: 403, name: "ClerkPlatformError" }); + expect(fetchImpl).toHaveBeenCalledOnce(); + await expect(Promise.reject(new ClerkPlatformError("no", 401))).rejects.toBeInstanceOf( + ClerkPlatformError + ); + }); +}); diff --git a/extension/__tests__/no-clerk-secret.test.ts b/extension/__tests__/no-clerk-secret.test.ts new file mode 100644 index 0000000..8eef352 --- /dev/null +++ b/extension/__tests__/no-clerk-secret.test.ts @@ -0,0 +1,45 @@ +import { readdirSync, readFileSync, statSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const extensionRoot = path.resolve(import.meta.dirname, ".."); + +const clientRoots = ["entrypoints", "services", "utils", "account"]; + +function sourceFiles(dir: string): string[] { + const entries = readdirSync(dir); + const files: string[] = []; + for (const entry of entries) { + const full = path.join(dir, entry); + const stat = statSync(full); + if (stat.isDirectory()) { + if (entry === "node_modules" || entry === "dist" || entry === ".wxt") continue; + files.push(...sourceFiles(full)); + continue; + } + if (/\.(ts|tsx|js|mjs|css|html)$/.test(entry)) files.push(full); + } + return files; +} + +describe("Clerk secrets stay out of the extension bundle sources", () => { + it("does not mention the secret env var in client code", () => { + const offenders: string[] = []; + for (const root of clientRoots) { + for (const file of sourceFiles(path.join(extensionRoot, root))) { + const text = readFileSync(file, "utf8"); + if ( + /import\.meta\.env\.CLERK_SECRET|process\.env\.CLERK_SECRET|process\.env\.CLERK_PLATFORM/.test( + text + ) + ) { + offenders.push(path.relative(extensionRoot, file)); + } + if (/sk_(test|live)_[A-Za-z0-9]/.test(text)) { + offenders.push(path.relative(extensionRoot, file)); + } + } + } + expect(offenders).toEqual([]); + }); +}); diff --git a/extension/__tests__/orders.test.ts b/extension/__tests__/orders.test.ts new file mode 100644 index 0000000..9a9e478 --- /dev/null +++ b/extension/__tests__/orders.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it, vi } from "vitest"; +import { + OrderHistoryError, + createHttpOrderSource, + createOrderHistorySource, + createPlaceholderOrderSource, + formatOrderMoney, + parseOrderHistoryPage, +} from "../account/orders"; + +const context = { getToken: async () => "session-token" }; + +describe("order history", () => { + it("returns labeled sample orders when no URL is configured", async () => { + const page = await createOrderHistorySource().listOrders({ userId: "user_123" }, context); + expect(page.source).toBe("placeholder"); + expect(page.orders.length).toBeGreaterThan(0); + expect(page.orders.every((order) => order.sample)).toBe(true); + expect(page.nextCursor).toBeNull(); + }); + + it("does not invent another page for the placeholder cursor", async () => { + const page = await createPlaceholderOrderSource().listOrders( + { userId: "user_123", cursor: "next" }, + context + ); + expect(page.orders).toEqual([]); + }); + + it("requires a user id", async () => { + await expect( + createPlaceholderOrderSource().listOrders({ userId: " " }, context) + ).rejects.toBeInstanceOf(OrderHistoryError); + }); + + it("requests the signed-in user's orders with the Clerk session token", async () => { + const fetchImpl = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => { + return new Response( + JSON.stringify({ + orders: [ + { + id: "ord_1", + number: "BH-9", + createdAt: "2026-11-01T00:00:00.000Z", + status: "paid", + currency: "USD", + totalAmountCents: 500, + lineItems: [ + { + id: "li_1", + name: "Seat", + quantity: 1, + unitAmountCents: 500, + currency: "usd", + }, + ], + }, + ], + nextCursor: "cursor-2", + }), + { status: 200, headers: { "Content-Type": "application/json" } } + ); + }); + const source = createHttpOrderSource( + "https://orders.example.com/v1/orders?tenant=bh", + fetchImpl + ); + const page = await source.listOrders( + { userId: "user_123", limit: 20, cursor: "cursor-1" }, + context + ); + + expect(page.source).toBe("http"); + expect(page.nextCursor).toBe("cursor-2"); + expect(page.orders[0]?.currency).toBe("usd"); + expect(page.orders[0]?.sample).toBeUndefined(); + const [input, init] = fetchImpl.mock.calls[0] ?? []; + const url = new URL(String(input)); + expect(url.origin).toBe("https://orders.example.com"); + expect(url.searchParams.get("userId")).toBe("user_123"); + expect(url.searchParams.get("tenant")).toBe("bh"); + expect(url.searchParams.get("limit")).toBe("20"); + expect(url.searchParams.get("cursor")).toBe("cursor-1"); + expect(init).toBeDefined(); + expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer session-token"); + }); + + it("does not fall back to sample orders when the HTTP source fails", async () => { + const fetchImpl = vi.fn(async () => new Response("nope", { status: 503 })); + const source = createHttpOrderSource("https://orders.example.com/orders", fetchImpl); + await expect(source.listOrders({ userId: "user_123" }, context)).rejects.toThrow(/503/); + }); + + it("rejects a malformed order page", () => { + expect(() => parseOrderHistoryPage({ orders: [{ id: "x" }] })).toThrow(OrderHistoryError); + }); + + it("formats cents as currency", () => { + expect(formatOrderMoney(2900, "usd")).toBe("$29.00"); + }); + + it("refuses a non-http order URL", () => { + expect(() => createHttpOrderSource("file:///tmp/orders.json")).toThrow(OrderHistoryError); + }); +}); diff --git a/extension/account/brand.ts b/extension/account/brand.ts new file mode 100644 index 0000000..9bbca9c --- /dev/null +++ b/extension/account/brand.ts @@ -0,0 +1,71 @@ +import type { Appearance } from "@clerk/ui/internal"; + +/** + * Browser Hand palette, locked in docs/brand/INDEX.md. + * Bone ground, ink text, cyan spark as the Clerk primary. + */ +export const BROWSER_HAND_COLORS = { + ink: "#0B0A12", + inkRaised: "#15111F", + bone: "#F7F4EE", + boneDeep: "#E7E1D6", + mist: "#5C574F", + cyan: "#7EE7FF", + green: "#4CAF50", +} as const; + +export const BROWSER_HAND_APPLICATION_NAME = "Browser Hand"; + +const FONT = '"Avenir Next", "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif'; + +/** + * Clerk `appearance` for prebuilt SignIn, SignUp, UserButton, and UserProfile. + * `logoUrl` should be an extension `chrome-extension://` URL for the glove mark. + * Component theming is client-side. Email and Account Portal branding (application + * name and logo) are applied with `npm run clerk:brand`, which calls Clerk's Backend + * and Platform APIs. + */ +export function browserHandAppearance(logoUrl: string): Appearance { + return { + variables: { + colorPrimary: BROWSER_HAND_COLORS.cyan, + colorPrimaryForeground: BROWSER_HAND_COLORS.ink, + colorForeground: BROWSER_HAND_COLORS.ink, + colorMutedForeground: BROWSER_HAND_COLORS.mist, + colorBackground: "#FFFFFF", + colorInput: "#FFFFFF", + colorInputForeground: BROWSER_HAND_COLORS.ink, + colorNeutral: BROWSER_HAND_COLORS.inkRaised, + colorBorder: BROWSER_HAND_COLORS.boneDeep, + colorDanger: "#EF4444", + colorSuccess: BROWSER_HAND_COLORS.green, + colorWarning: "#E6A23C", + fontFamily: FONT, + fontFamilyButtons: FONT, + borderRadius: "0.625rem", + }, + options: { + logoImageUrl: logoUrl, + logoLinkUrl: "https://github.com/verygoodplugins/browser-hand", + logoPlacement: "inside", + socialButtonsVariant: "blockButton", + }, + elements: { + card: { + border: `1px solid ${BROWSER_HAND_COLORS.boneDeep}`, + boxShadow: "none", + }, + headerTitle: { + color: BROWSER_HAND_COLORS.ink, + fontWeight: 600, + }, + headerSubtitle: { + color: BROWSER_HAND_COLORS.mist, + }, + formButtonPrimary: { + color: BROWSER_HAND_COLORS.ink, + fontWeight: 600, + }, + }, + }; +} diff --git a/extension/account/clerk-setup.ts b/extension/account/clerk-setup.ts new file mode 100644 index 0000000..dc4c5fe --- /dev/null +++ b/extension/account/clerk-setup.ts @@ -0,0 +1,186 @@ +/** + * Pure helpers for `npm run clerk:brand`. + * + * Instance allowed origins go through `@clerk/backend` (`instance.update`, + * which wraps `PATCH /v1/instance`). Application name and logo go through + * Clerk's Platform API (`PATCH /platform/applications/{id}` and + * `POST /platform/applications/{id}/logo`). The Platform API is a separate + * credential from the instance secret and may need to be enabled on the account. + */ + +export const CLERK_API_BASE = "https://api.clerk.com/v1"; +export const BROWSER_HAND_APPLICATION_NAME = "Browser Hand"; + +export interface ClerkInstanceSnapshot { + allowedOrigins: string[] | null; +} + +export interface ClerkInstanceClient { + get(): Promise; + update(params: { allowedOrigins: string[] }): Promise; +} + +const CHROME_EXTENSION_ID = /^[a-p]{32}$/; + +export function chromeExtensionOrigin(extensionId: string): string { + const id = extensionId + .trim() + .replace(/^chrome-extension:\/\//i, "") + .replace(/\/$/, ""); + if (!CHROME_EXTENSION_ID.test(id)) { + throw new Error( + `CLERK_EXTENSION_ID must be the 32-character id from chrome://extensions (letters a-p). Received "${extensionId}".` + ); + } + return `chrome-extension://${id}`; +} + +export function normalizeOrigin(value: string): string { + const trimmed = value.trim(); + if (!trimmed) { + throw new Error("Allowed origin is empty."); + } + if (trimmed.toLowerCase().startsWith("chrome-extension://")) { + return chromeExtensionOrigin(trimmed); + } + let url: URL; + try { + url = new URL(trimmed); + } catch { + throw new Error(`Not an origin: ${value}`); + } + if (url.protocol !== "https:" && url.protocol !== "http:") { + throw new Error(`Origin must be http(s) or chrome-extension: ${value}`); + } + return url.origin; +} + +/** Keep existing origins and append any that are missing. Clerk replaces the whole list on update. */ +export function mergeAllowedOrigins( + existing: readonly string[] | null | undefined, + required: readonly string[] +): string[] { + const seen = new Set(); + const merged: string[] = []; + for (const origin of [...(existing ?? []), ...required]) { + const trimmed = origin.trim(); + if (!trimmed || seen.has(trimmed)) continue; + seen.add(trimmed); + merged.push(trimmed); + } + return merged; +} + +export function parseOriginList(value: string | undefined): string[] { + if (!value?.trim()) return []; + return value + .split(",") + .map((part) => part.trim()) + .filter(Boolean) + .map((part) => normalizeOrigin(part)); +} + +export async function syncAllowedOrigins( + client: ClerkInstanceClient, + originsToAdd: readonly string[] +): Promise { + const current = await client.get(); + const merged = mergeAllowedOrigins(current.allowedOrigins, originsToAdd); + await client.update({ allowedOrigins: merged }); + return merged; +} + +export interface PlatformRequest { + url: string; + init: RequestInit; +} + +export function applicationNameRequest( + applicationId: string, + token: string, + name: string = BROWSER_HAND_APPLICATION_NAME, + baseUrl: string = CLERK_API_BASE +): PlatformRequest { + return { + url: `${baseUrl}/platform/applications/${encodeURIComponent(applicationId)}`, + init: { + method: "PATCH", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ name }), + }, + }; +} + +export function logoFormData(bytes: Uint8Array, filename: string): FormData { + const copy = new Uint8Array(bytes.byteLength); + copy.set(bytes); + const form = new FormData(); + form.set("file", new Blob([copy], { type: "image/png" }), filename); + return form; +} + +export function applicationLogoRequest( + applicationId: string, + token: string, + bytes: Uint8Array, + filename: string, + baseUrl: string = CLERK_API_BASE +): PlatformRequest { + return { + url: `${baseUrl}/platform/applications/${encodeURIComponent(applicationId)}/logo`, + init: { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + }, + body: logoFormData(bytes, filename), + }, + }; +} + +export function applicationFaviconRequest( + applicationId: string, + token: string, + bytes: Uint8Array, + filename: string, + baseUrl: string = CLERK_API_BASE +): PlatformRequest { + return { + url: `${baseUrl}/platform/applications/${encodeURIComponent(applicationId)}/favicon`, + init: { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + }, + body: logoFormData(bytes, filename), + }, + }; +} + +export class ClerkPlatformError extends Error { + readonly status: number; + + constructor(message: string, status: number) { + super(message); + this.name = "ClerkPlatformError"; + this.status = status; + } +} + +export async function sendPlatformRequest( + request: PlatformRequest, + fetchImpl: typeof fetch = fetch +): Promise { + const response = await fetchImpl(request.url, request.init); + if (response.ok) return; + const detail = (await response.text()).trim().slice(0, 500); + throw new ClerkPlatformError( + detail + ? `Clerk Platform API ${request.init.method} ${request.url} failed (${response.status}): ${detail}` + : `Clerk Platform API ${request.init.method} ${request.url} failed (${response.status}).`, + response.status + ); +} diff --git a/extension/account/config.ts b/extension/account/config.ts new file mode 100644 index 0000000..b895289 --- /dev/null +++ b/extension/account/config.ts @@ -0,0 +1,62 @@ +/** + * Public Clerk configuration inlined into the extension at build time. + * + * Only `WXT_` variables are read. The instance secret stays in the Node brand + * script; a `WXT_`-prefixed secret would be inlined into the extension bundle. + */ + +export interface PublicClerkEnv { + WXT_CLERK_PUBLISHABLE_KEY?: string; + WXT_CLERK_SYNC_HOST?: string; + WXT_CLERK_FRONTEND_API?: string; + WXT_ORDER_HISTORY_URL?: string; +} + +export interface BrowserHandClerkConfig { + /** Clerk publishable key (`pk_test_…` or `pk_live_…`). Safe to ship in the extension. */ + publishableKey?: string; + /** + * Optional website origin that shares this Clerk session with the extension. + * Leave unset when the extension is the only signed-in surface. + */ + syncHost?: string; + /** Frontend API URL from the Clerk API keys page. Informational; the publishable key already encodes it. */ + frontendApi?: string; + /** When set, order history is loaded from this HTTP endpoint instead of the local sample source. */ + orderHistoryUrl?: string; +} + +function clean(value: string | undefined): string | undefined { + const trimmed = value?.trim(); + return trimmed ? trimmed : undefined; +} + +/** + * A publishable key is configured when it looks like a real Clerk key. + * The placeholder in `.env.example` is treated as unset so the account page + * can explain setup instead of calling Clerk with a dummy key. + */ +export function isConfiguredPublishableKey(key: string | undefined): key is string { + if (!key) return false; + if (!key.startsWith("pk_test_") && !key.startsWith("pk_live_")) return false; + if (key.includes("replace_me")) return false; + return true; +} + +export function readClerkConfig(env?: PublicClerkEnv): BrowserHandClerkConfig { + // Property access is static on purpose: Vite inlines `import.meta.env.WXT_*` + // only for direct reads, not for `import.meta.env[name]`. + const source = env ?? { + WXT_CLERK_PUBLISHABLE_KEY: import.meta.env.WXT_CLERK_PUBLISHABLE_KEY, + WXT_CLERK_SYNC_HOST: import.meta.env.WXT_CLERK_SYNC_HOST, + WXT_CLERK_FRONTEND_API: import.meta.env.WXT_CLERK_FRONTEND_API, + WXT_ORDER_HISTORY_URL: import.meta.env.WXT_ORDER_HISTORY_URL, + }; + + return { + publishableKey: clean(source.WXT_CLERK_PUBLISHABLE_KEY), + syncHost: clean(source.WXT_CLERK_SYNC_HOST), + frontendApi: clean(source.WXT_CLERK_FRONTEND_API), + orderHistoryUrl: clean(source.WXT_ORDER_HISTORY_URL), + }; +} diff --git a/extension/account/orders.ts b/extension/account/orders.ts new file mode 100644 index 0000000..752d60b --- /dev/null +++ b/extension/account/orders.ts @@ -0,0 +1,347 @@ +/** + * Order history for the Browser Hand account page. + * + * There is no order service in this repo. `createOrderHistorySource()` returns: + * - an HTTP source when `WXT_ORDER_HISTORY_URL` is set at build time + * - otherwise a placeholder source with clearly marked sample orders + * + * Wire a real source by implementing `GET {WXT_ORDER_HISTORY_URL}`: + * + * ```json + * { "orders": [Order], "nextCursor": null } + * ``` + * + * The extension sends `Authorization: Bearer ` and + * `userId`, `limit`, and `cursor` query parameters. The server must verify the + * token with `@clerk/backend` `verifyToken` and return only that user's orders + * (`payload.sub`). Do not trust `userId` by itself. See the README. + */ + +export type OrderStatus = "pending" | "paid" | "fulfilled" | "cancelled" | "refunded"; + +export interface OrderLineItem { + id: string; + name: string; + quantity: number; + /** Integer minor units (cents). */ + unitAmountCents: number; + currency: string; +} + +export interface Order { + id: string; + number: string; + /** ISO-8601 timestamp. */ + createdAt: string; + status: OrderStatus; + currency: string; + /** Integer minor units (cents). */ + totalAmountCents: number; + lineItems: OrderLineItem[]; + /** True only for the built-in sample source. Real orders omit this. */ + sample?: boolean; +} + +export interface OrderHistoryQuery { + userId: string; + limit?: number; + cursor?: string; +} + +export interface OrderHistoryPage { + orders: Order[]; + nextCursor: string | null; + source: "placeholder" | "http"; +} + +export interface OrderHistoryContext { + /** Clerk session token. Required by the HTTP source. */ + getToken: () => Promise; +} + +export interface OrderHistorySource { + readonly id: OrderHistoryPage["source"]; + listOrders(query: OrderHistoryQuery, context: OrderHistoryContext): Promise; +} + +export class OrderHistoryError extends Error { + readonly status?: number; + + constructor(message: string, status?: number) { + super(message); + this.name = "OrderHistoryError"; + this.status = status; + } +} + +const ORDER_STATUSES: readonly OrderStatus[] = [ + "pending", + "paid", + "fulfilled", + "cancelled", + "refunded", +]; + +const PLACEHOLDER_ORDERS: readonly Order[] = [ + { + id: "sample_bh_1108", + number: "BH-1108", + createdAt: "2026-10-02T18:24:00.000Z", + status: "paid", + currency: "usd", + totalAmountCents: 1200, + sample: true, + lineItems: [ + { + id: "sample_li_1108", + name: "Browser Hand relay add-on (sample)", + quantity: 1, + unitAmountCents: 1200, + currency: "usd", + }, + ], + }, + { + id: "sample_bh_1042", + number: "BH-1042", + createdAt: "2026-07-18T15:04:00.000Z", + status: "fulfilled", + currency: "usd", + totalAmountCents: 2900, + sample: true, + lineItems: [ + { + id: "sample_li_1042", + name: "Browser Hand seat (sample)", + quantity: 1, + unitAmountCents: 2900, + currency: "usd", + }, + ], + }, +]; + +function requireUserId(userId: string): string { + const trimmed = userId.trim(); + if (!trimmed) { + throw new OrderHistoryError("A Clerk user id is required to list orders."); + } + return trimmed; +} + +export function createPlaceholderOrderSource(): OrderHistorySource { + return { + id: "placeholder", + async listOrders(query) { + requireUserId(query.userId); + if (query.cursor) { + return { orders: [], nextCursor: null, source: "placeholder" }; + } + const limit = query.limit ?? PLACEHOLDER_ORDERS.length; + return { + orders: PLACEHOLDER_ORDERS.slice(0, limit).map((order) => ({ + ...order, + lineItems: order.lineItems.map((item) => ({ ...item })), + })), + nextCursor: null, + source: "placeholder", + }; + }, + }; +} + +export function createHttpOrderSource( + orderHistoryUrl: string, + fetchImpl: typeof fetch = fetch +): OrderHistorySource { + let endpoint: URL; + try { + endpoint = new URL(orderHistoryUrl); + } catch { + throw new OrderHistoryError(`Order history URL is not a valid URL: ${orderHistoryUrl}`); + } + if (endpoint.protocol !== "https:" && endpoint.protocol !== "http:") { + throw new OrderHistoryError("Order history URL must use http or https."); + } + + return { + id: "http", + async listOrders(query, context) { + const userId = requireUserId(query.userId); + const token = await context.getToken(); + if (!token) { + throw new OrderHistoryError("Sign in again to load orders."); + } + + const url = new URL(endpoint.toString()); + url.searchParams.set("userId", userId); + if (query.limit != null) url.searchParams.set("limit", String(query.limit)); + if (query.cursor) url.searchParams.set("cursor", query.cursor); + + let response: Response; + try { + response = await fetchImpl(url, { + method: "GET", + headers: { + Accept: "application/json", + Authorization: `Bearer ${token}`, + }, + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new OrderHistoryError(`Order history request failed: ${message}`); + } + + if (!response.ok) { + const detail = (await response.text()).trim().slice(0, 300); + throw new OrderHistoryError( + detail + ? `Order history request failed (${response.status}): ${detail}` + : `Order history request failed (${response.status}).`, + response.status + ); + } + + let body: unknown; + try { + body = await response.json(); + } catch { + throw new OrderHistoryError("Order history response was not JSON."); + } + + const page = parseOrderHistoryPage(body); + return { ...page, source: "http" }; + }, + }; +} + +export function createOrderHistorySource(orderHistoryUrl?: string): OrderHistorySource { + const url = orderHistoryUrl?.trim(); + if (!url) return createPlaceholderOrderSource(); + return createHttpOrderSource(url); +} + +export function parseOrderHistoryPage(body: unknown): Omit { + if (!body || typeof body !== "object") { + throw new OrderHistoryError("Order history response must be a JSON object."); + } + const record = body as { orders?: unknown; nextCursor?: unknown }; + if (!Array.isArray(record.orders)) { + throw new OrderHistoryError("Order history response is missing an orders array."); + } + if (record.nextCursor != null && typeof record.nextCursor !== "string") { + throw new OrderHistoryError("Order history nextCursor must be a string or null."); + } + return { + orders: record.orders.map((order, index) => parseOrder(order, index)), + nextCursor: record.nextCursor ?? null, + }; +} + +function parseOrder(value: unknown, index: number): Order { + const order = expectRecord(value, `orders[${index}]`); + const currency = expectCurrency(order.currency, `orders[${index}].currency`); + const lineItems = Array.isArray(order.lineItems) + ? order.lineItems.map((item, itemIndex) => parseLineItem(item, index, itemIndex)) + : null; + if (!lineItems) { + throw new OrderHistoryError(`orders[${index}].lineItems must be an array.`); + } + return { + id: expectId(order.id, `orders[${index}].id`), + number: expectId(order.number, `orders[${index}].number`), + createdAt: expectIsoDate(order.createdAt, `orders[${index}].createdAt`), + status: expectStatus(order.status, `orders[${index}].status`), + currency, + totalAmountCents: expectCents(order.totalAmountCents, `orders[${index}].totalAmountCents`), + lineItems, + }; +} + +function parseLineItem(value: unknown, orderIndex: number, itemIndex: number): OrderLineItem { + const path = `orders[${orderIndex}].lineItems[${itemIndex}]`; + const item = expectRecord(value, path); + const currency = expectCurrency(item.currency, `${path}.currency`); + return { + id: expectId(item.id, `${path}.id`), + name: expectId(item.name, `${path}.name`), + quantity: expectQuantity(item.quantity, `${path}.quantity`), + unitAmountCents: expectCents(item.unitAmountCents, `${path}.unitAmountCents`), + currency, + }; +} + +function expectRecord(value: unknown, path: string): Record { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new OrderHistoryError(`${path} must be an object.`); + } + return value as Record; +} + +function expectId(value: unknown, path: string): string { + if (typeof value !== "string" || !value.trim()) { + throw new OrderHistoryError(`${path} must be a non-empty string.`); + } + return value; +} + +function expectIsoDate(value: unknown, path: string): string { + if (typeof value !== "string" || Number.isNaN(Date.parse(value))) { + throw new OrderHistoryError(`${path} must be an ISO-8601 date.`); + } + return value; +} + +function expectStatus(value: unknown, path: string): OrderStatus { + if (typeof value !== "string" || !ORDER_STATUSES.includes(value as OrderStatus)) { + throw new OrderHistoryError(`${path} must be one of ${ORDER_STATUSES.join(", ")}.`); + } + return value as OrderStatus; +} + +function expectCurrency(value: unknown, path: string): string { + if (typeof value !== "string" || !/^[A-Za-z]{3}$/.test(value)) { + throw new OrderHistoryError(`${path} must be a 3-letter currency code.`); + } + return value.toLowerCase(); +} + +function expectCents(value: unknown, path: string): number { + if (typeof value !== "number" || !Number.isInteger(value)) { + throw new OrderHistoryError(`${path} must be an integer number of cents.`); + } + return value; +} + +function expectQuantity(value: unknown, path: string): number { + if (typeof value !== "number" || !Number.isInteger(value) || value < 1) { + throw new OrderHistoryError(`${path} must be a positive integer.`); + } + return value; +} + +export function formatOrderMoney(amountCents: number, currency: string): string { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: currency.toUpperCase(), + }).format(amountCents / 100); +} + +export function formatOrderDate(iso: string): string { + return new Intl.DateTimeFormat("en-US", { dateStyle: "medium" }).format(new Date(iso)); +} + +export function orderStatusLabel(status: OrderStatus): string { + switch (status) { + case "pending": + return "Pending"; + case "paid": + return "Paid"; + case "fulfilled": + return "Fulfilled"; + case "cancelled": + return "Cancelled"; + case "refunded": + return "Refunded"; + } +} diff --git a/extension/account/session.ts b/extension/account/session.ts new file mode 100644 index 0000000..2b7381e --- /dev/null +++ b/extension/account/session.ts @@ -0,0 +1,68 @@ +/** + * Display cache of the Clerk session for the toolbar popup. + * + * The account page is the session authority (ClerkProvider). It writes this + * snapshot into `chrome.storage.local` whenever Clerk finishes loading. + * The popup reads it so the toolbar does not have to boot Clerk on every open. + * Tokens are never stored here. + */ + +export const ACCOUNT_SNAPSHOT_KEY = "browserHand.account"; + +export interface AccountSnapshot { + signedIn: boolean; + userId: string | null; + email: string | null; + name: string | null; + imageUrl: string | null; + updatedAt: string; +} + +/** Subset of Clerk's user resource the account page knows how to mirror. */ +export interface ClerkUserLike { + id: string; + fullName: string | null; + imageUrl: string; + primaryEmailAddress: { emailAddress: string } | null; +} + +export function signedOutSnapshot(now: Date = new Date()): AccountSnapshot { + return { + signedIn: false, + userId: null, + email: null, + name: null, + imageUrl: null, + updatedAt: now.toISOString(), + }; +} + +export function snapshotFromUser(user: ClerkUserLike, now: Date = new Date()): AccountSnapshot { + const email = user.primaryEmailAddress?.emailAddress?.trim() || null; + const name = user.fullName?.trim() || null; + return { + signedIn: true, + userId: user.id, + email, + name, + imageUrl: user.imageUrl?.trim() || null, + updatedAt: now.toISOString(), + }; +} + +export function isAccountSnapshot(value: unknown): value is AccountSnapshot { + if (!value || typeof value !== "object") return false; + const snapshot = value as Partial; + return ( + typeof snapshot.signedIn === "boolean" && + (snapshot.userId === null || typeof snapshot.userId === "string") && + (snapshot.email === null || typeof snapshot.email === "string") && + (snapshot.name === null || typeof snapshot.name === "string") && + (snapshot.imageUrl === null || typeof snapshot.imageUrl === "string") && + typeof snapshot.updatedAt === "string" + ); +} + +export function accountPageUrl(): string { + return chrome.runtime.getURL("account.html"); +} diff --git a/extension/entrypoints/account/App.tsx b/extension/entrypoints/account/App.tsx new file mode 100644 index 0000000..f3169a0 --- /dev/null +++ b/extension/entrypoints/account/App.tsx @@ -0,0 +1,285 @@ +import { + ClerkFailed, + ClerkLoaded, + ClerkLoading, + ClerkProvider, + Show, + SignIn, + SignUp, + UserButton, + UserProfile, + useClerk, + useUser, +} from "@clerk/chrome-extension"; +import { useEffect, useState, type ReactNode } from "react"; +import { browserHandAppearance } from "../../account/brand"; +import { isConfiguredPublishableKey, readClerkConfig } from "../../account/config"; +import { + ACCOUNT_SNAPSHOT_KEY, + accountPageUrl, + signedOutSnapshot, + snapshotFromUser, +} from "../../account/session"; +import { OrderHistory } from "./OrderHistory"; + +function logoUrl(): string { + return chrome.runtime.getURL("icons/icon-128.png"); +} + +export default function App() { + const config = readClerkConfig(); + const pageUrl = accountPageUrl(); + + if (!isConfiguredPublishableKey(config.publishableKey)) { + return ( + + + + ); + } + + return ( + + + + +

Loading account…

+
+ +

+ Clerk could not start. Check the publishable key, rebuild the extension, and confirm + this extension's chrome-extension:// origin is in the instance allowed + origins (npm run clerk:brand). +

+
+ + + + + + + + +

+ The toolbar toggle still drives your local relay whether or not this account is signed in. +

+
+
+ ); +} + +function Shell({ children }: { children: ReactNode }) { + return ( +
+
+ +
+

Browser Hand

+

Account

+
+
+ {children} +
+ ); +} + +function SetupRequired() { + return ( +
+

Sign-in is not configured

+

+ Browser Hand uses Clerk for sign-in, sign-up, and this account page. Add a publishable key + and rebuild the extension. The local automation relay keeps working without it. +

+
    +
  1. + Create a Clerk application named Browser Hand and enable the Native API. +
  2. +
  3. + Copy .env.example to .env at the repo root. Set{" "} + WXT_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY. +
  4. +
  5. + Rebuild (npm run build:extension) and reload the unpacked extension. Keys are + inlined at build time. +
  6. +
  7. + Copy the extension id from chrome://extensions into{" "} + CLERK_EXTENSION_ID and run npm run clerk:brand so Clerk allows + this chrome-extension:// origin. +
  8. +
+
+ ); +} + +function SessionMirror() { + const { isLoaded, isSignedIn, user } = useUser(); + useEffect(() => { + if (!isLoaded) return; + const snapshot = + isSignedIn && user + ? snapshotFromUser({ + id: user.id, + fullName: user.fullName, + imageUrl: user.imageUrl, + primaryEmailAddress: user.primaryEmailAddress + ? { emailAddress: user.primaryEmailAddress.emailAddress } + : null, + }) + : signedOutSnapshot(); + void chrome.storage.local.set({ [ACCOUNT_SNAPSHOT_KEY]: snapshot }); + }, [ + isLoaded, + isSignedIn, + user?.id, + user?.fullName, + user?.imageUrl, + user?.primaryEmailAddress?.emailAddress, + user?.updatedAt, + ]); + return null; +} + +function readAuthView(): "sign-in" | "sign-up" { + return new URLSearchParams(window.location.search).get("auth") === "sign-up" + ? "sign-up" + : "sign-in"; +} + +function useAuthView(): "sign-in" | "sign-up" { + const [view, setView] = useState<"sign-in" | "sign-up">(readAuthView); + useEffect(() => { + const onChange = () => setView(readAuthView()); + window.addEventListener("hashchange", onChange); + window.addEventListener("popstate", onChange); + return () => { + window.removeEventListener("hashchange", onChange); + window.removeEventListener("popstate", onChange); + }; + }, []); + return view; +} + +function showAuthView(next: "sign-in" | "sign-up"): void { + const url = new URL(window.location.href); + url.searchParams.set("auth", next); + url.hash = ""; + window.history.pushState(null, "", url); + window.dispatchEvent(new PopStateEvent("popstate")); +} + +function SignedOutPanel({ pageUrl }: { pageUrl: string }) { + const view = useAuthView(); + const signInUrl = `${pageUrl}?auth=sign-in`; + const signUpUrl = `${pageUrl}?auth=sign-up`; + + return ( +
+
+ + +
+ {view === "sign-in" ? ( + + ) : ( + + )} +
+ ); +} + +function SignedInPanel() { + const { user } = useUser(); + const clerk = useClerk(); + useEffect(() => { + const hash = window.location.hash; + if (hash.includes("sign-in") || hash.includes("sign-up")) { + const url = new URL(window.location.href); + url.hash = ""; + window.history.replaceState(null, "", url); + } + }, []); + if (!user) return null; + + const created = user.createdAt + ? new Intl.DateTimeFormat("en-US", { dateStyle: "medium" }).format(user.createdAt) + : null; + + async function signOut(): Promise { + await chrome.storage.local.set({ [ACCOUNT_SNAPSHOT_KEY]: signedOutSnapshot() }); + await clerk.signOut({ redirectUrl: accountPageUrl() }); + } + + return ( + <> +
+ +
+
+

Account

+
+
Name
+
{user.fullName || "—"}
+
Email
+
{user.primaryEmailAddress?.emailAddress || "—"}
+
User id
+
{user.id}
+ {created ? ( + <> +
Member since
+
{created}
+ + ) : null} +
+
+ +
+
+ +
+

Profile and security

+ +
+ + ); +} diff --git a/extension/entrypoints/account/OrderHistory.tsx b/extension/entrypoints/account/OrderHistory.tsx new file mode 100644 index 0000000..7857353 --- /dev/null +++ b/extension/entrypoints/account/OrderHistory.tsx @@ -0,0 +1,125 @@ +import { useAuth } from "@clerk/chrome-extension"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { readClerkConfig } from "../../account/config"; +import { + createOrderHistorySource, + formatOrderDate, + formatOrderMoney, + orderStatusLabel, + type Order, + type OrderHistorySource, +} from "../../account/orders"; + +export function OrderHistory({ userId }: { userId: string }) { + const { getToken } = useAuth(); + const getTokenRef = useRef(getToken); + getTokenRef.current = getToken; + + const source = useMemo( + () => createOrderHistorySource(readClerkConfig().orderHistoryUrl), + [] + ); + const [orders, setOrders] = useState([]); + const [nextCursor, setNextCursor] = useState(null); + const [sourceId, setSourceId] = useState(source.id); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + setLoading(true); + setError(null); + source + .listOrders({ userId, limit: 20 }, { getToken: () => getTokenRef.current() }) + .then((page) => { + if (cancelled) return; + setOrders(page.orders); + setNextCursor(page.nextCursor); + setSourceId(page.source); + }) + .catch((err: unknown) => { + if (cancelled) return; + setError(err instanceof Error ? err.message : "Could not load orders."); + }) + .finally(() => { + if (!cancelled) setLoading(false); + }); + return () => { + cancelled = true; + }; + }, [source, userId]); + + async function loadMore(): Promise { + if (!nextCursor) return; + setLoading(true); + setError(null); + try { + const page = await source.listOrders( + { userId, limit: 20, cursor: nextCursor }, + { getToken: () => getTokenRef.current() } + ); + setOrders((current) => [...current, ...page.orders]); + setNextCursor(page.nextCursor); + setSourceId(page.source); + } catch (err) { + setError(err instanceof Error ? err.message : "Could not load orders."); + } finally { + setLoading(false); + } + } + + return ( +
+

Order history

+ {sourceId === "placeholder" && !error ? ( +

+ Sample orders. Browser Hand does not have an order service yet. These + rows are placeholders so the account page has a shape to render. Set{" "} + WXT_ORDER_HISTORY_URL and rebuild to load this account's real orders. +

+ ) : null} + {error ?

{error}

: null} + {loading && orders.length === 0 ?

Loading orders…

: null} + {!loading && !error && orders.length === 0 ? ( +

No orders for this account.

+ ) : null} + {orders.length > 0 ? ( +
    + {orders.map((order) => ( +
  • +
    + {order.number} + + {orderStatusLabel(order.status)} + +
    +
    + {formatOrderDate(order.createdAt)} ·{" "} + {formatOrderMoney(order.totalAmountCents, order.currency)} +
    +
      + {order.lineItems.map((item) => ( +
    • + {item.quantity} × {item.name} +
    • + ))} +
    +
  • + ))} +
+ ) : null} + {nextCursor ? ( +
+ +
+ ) : null} +
+ ); +} diff --git a/extension/entrypoints/account/index.html b/extension/entrypoints/account/index.html new file mode 100644 index 0000000..9815a7f --- /dev/null +++ b/extension/entrypoints/account/index.html @@ -0,0 +1,12 @@ + + + + + + Browser Hand account + + +
+ + + diff --git a/extension/entrypoints/account/main.tsx b/extension/entrypoints/account/main.tsx new file mode 100644 index 0000000..ab9474f --- /dev/null +++ b/extension/entrypoints/account/main.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./style.css"; + +const root = document.getElementById("root"); +if (!root) { + throw new Error("Browser Hand account page is missing its root element."); +} + +ReactDOM.createRoot(root).render( + + + +); diff --git a/extension/entrypoints/account/style.css b/extension/entrypoints/account/style.css new file mode 100644 index 0000000..b4362b2 --- /dev/null +++ b/extension/entrypoints/account/style.css @@ -0,0 +1,290 @@ +:root { + color-scheme: light; + --ink: #0b0a12; + --ink-raised: #15111f; + --bone: #f7f4ee; + --bone-deep: #e7e1d6; + --mist: #5c574f; + --cyan: #7ee7ff; + --green: #4caf50; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background: var(--bone); + color: var(--ink); + font-family: "Avenir Next", "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.45; +} + +button, +input { + font: inherit; +} + +.shell { + max-width: 960px; + margin: 0 auto; + padding: 32px 20px 72px; +} + +.topbar { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 28px; +} + +.mark { + width: 44px; + height: 44px; + border-radius: 10px; + flex: none; +} + +.wordmark { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.wordmark h1 { + margin: 0; + font-size: 22px; + font-weight: 650; + letter-spacing: -0.03em; +} + +.wordmark p { + margin: 0; + color: var(--mist); + font-size: 14px; +} + +.topbar-actions { + margin-left: auto; + display: flex; + align-items: center; + gap: 10px; +} + +.panel { + background: #fff; + border: 1px solid var(--bone-deep); + border-radius: 14px; + padding: 20px; +} + +.panel + .panel, +.banner + .panel, +.panel + .banner { + margin-top: 16px; +} + +.panel h2 { + margin: 0 0 12px; + font-size: 16px; + letter-spacing: -0.02em; +} + +.auth-layout { + max-width: 440px; + margin: 0 auto; +} + +.auth-tabs { + display: flex; + gap: 8px; + margin-bottom: 16px; +} + +.auth-tabs button { + flex: 1; + border: 1px solid var(--bone-deep); + background: transparent; + color: var(--ink); + border-radius: 999px; + padding: 8px 12px; + cursor: pointer; +} + +.auth-tabs button[aria-selected="true"] { + background: var(--ink); + color: var(--cyan); + border-color: var(--ink); +} + +.account-grid { + display: grid; + grid-template-columns: 180px 1fr; + gap: 8px 16px; + margin: 0; +} + +.account-grid dt { + margin: 0; + color: var(--mist); + font-size: 13px; +} + +.account-grid dd { + margin: 0; + font-size: 14px; + overflow-wrap: anywhere; +} + +.mono { + font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; + font-size: 12px; +} + +.row-actions { + display: flex; + gap: 8px; + margin-top: 16px; +} + +.button { + border: 1px solid var(--bone-deep); + background: #fff; + color: var(--ink); + border-radius: 8px; + padding: 8px 12px; + cursor: pointer; +} + +.button.primary { + background: var(--cyan); + border-color: var(--cyan); + color: var(--ink); + font-weight: 600; +} + +.banner { + border-radius: 12px; + padding: 12px 14px; + background: #fff; + border: 1px solid var(--bone-deep); + color: var(--mist); + font-size: 14px; +} + +.banner strong { + color: var(--ink); + font-weight: 600; +} + +.orders { + display: flex; + flex-direction: column; + gap: 12px; + margin: 0; + padding: 0; + list-style: none; +} + +.order { + border: 1px solid var(--bone-deep); + border-radius: 12px; + padding: 12px 14px; +} + +.order-head { + display: flex; + justify-content: space-between; + gap: 12px; + align-items: baseline; +} + +.order-number { + font-weight: 650; +} + +.order-meta { + margin-top: 4px; + color: var(--mist); + font-size: 13px; +} + +.order-lines { + margin: 8px 0 0; + padding: 0; + list-style: none; + font-size: 14px; +} + +.status { + font-size: 12px; + font-weight: 650; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.status-paid, +.status-fulfilled { + color: #1f7a34; +} + +.status-pending { + color: #8a5a00; +} + +.status-cancelled, +.status-refunded { + color: var(--mist); +} + +.muted { + color: var(--mist); +} + +.error { + color: #9f1239; +} + +.setup code, +.banner code { + font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; + font-size: 0.92em; +} + +.setup ol { + margin: 12px 0 0; + padding-left: 18px; +} + +.setup li + li { + margin-top: 8px; +} + +.footnote { + margin-top: 28px; + color: var(--mist); + font-size: 13px; +} + +@media (max-width: 640px) { + .account-grid { + grid-template-columns: 1fr; + gap: 2px; + } + + .account-grid dt { + margin-top: 8px; + } + + .topbar { + align-items: flex-start; + } + + .topbar-actions { + margin-left: 0; + } +} diff --git a/extension/entrypoints/popup/index.html b/extension/entrypoints/popup/index.html index f471234..85f5eeb 100644 --- a/extension/entrypoints/popup/index.html +++ b/extension/entrypoints/popup/index.html @@ -42,6 +42,17 @@

Browser Hand

+ + diff --git a/extension/entrypoints/popup/main.ts b/extension/entrypoints/popup/main.ts index e84b7ab..22f020d 100644 --- a/extension/entrypoints/popup/main.ts +++ b/extension/entrypoints/popup/main.ts @@ -1,3 +1,4 @@ +import { ACCOUNT_SNAPSHOT_KEY, accountPageUrl, isAccountSnapshot } from "../../account/session"; import type { FocusPolicy, GetStateMessage, @@ -105,3 +106,47 @@ focusPolicySelect.addEventListener("change", () => { } ); }); + +const accountName = document.getElementById("account-name") as HTMLDivElement; +const accountEmail = document.getElementById("account-email") as HTMLDivElement; +const accountAvatar = document.getElementById("account-avatar") as HTMLImageElement; +const openAccount = document.getElementById("open-account") as HTMLButtonElement; + +function renderAccount(value: unknown): void { + const snapshot = isAccountSnapshot(value) ? value : null; + if (snapshot?.signedIn) { + accountName.textContent = snapshot.name || snapshot.email || "Signed in"; + accountEmail.textContent = snapshot.email || "Account and orders"; + openAccount.textContent = "Account"; + if (snapshot.imageUrl) { + accountAvatar.src = snapshot.imageUrl; + accountAvatar.hidden = false; + } else { + accountAvatar.removeAttribute("src"); + accountAvatar.hidden = true; + } + return; + } + accountName.textContent = "Not signed in"; + accountEmail.textContent = "Account and orders"; + openAccount.textContent = "Sign in"; + accountAvatar.removeAttribute("src"); + accountAvatar.hidden = true; +} + +chrome.storage.local.get(ACCOUNT_SNAPSHOT_KEY, (result) => { + renderAccount(result[ACCOUNT_SNAPSHOT_KEY]); +}); + +chrome.storage.onChanged.addListener((changes, area) => { + if (area === "local" && changes[ACCOUNT_SNAPSHOT_KEY]) { + renderAccount(changes[ACCOUNT_SNAPSHOT_KEY].newValue); + } +}); + +openAccount.addEventListener("click", () => { + const url = accountPageUrl(); + chrome.tabs.create({ url }).catch(() => { + window.open(url, "_blank"); + }); +}); diff --git a/extension/entrypoints/popup/style.css b/extension/entrypoints/popup/style.css index a2099f7..4427b14 100644 --- a/extension/entrypoints/popup/style.css +++ b/extension/entrypoints/popup/style.css @@ -131,3 +131,46 @@ input:checked + .slider::before { line-height: 1.35; color: #888; } + +.account-row { + margin-top: 14px; + padding-top: 12px; + border-top: 1px solid #eee; +} + +.account-identity { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.account-avatar { + width: 24px; + height: 24px; + border-radius: 6px; + flex: none; +} + +.account-name { + font-size: 13px; + font-weight: 600; + color: #333; +} + +.account-email { + font-size: 11px; + color: #888; + overflow-wrap: anywhere; +} + +.account-button { + width: 100%; + font: inherit; + padding: 6px 8px; + border: 1px solid #ccc; + border-radius: 4px; + background: #fff; + color: #333; + cursor: pointer; +} diff --git a/extension/env.d.ts b/extension/env.d.ts new file mode 100644 index 0000000..01535e0 --- /dev/null +++ b/extension/env.d.ts @@ -0,0 +1,11 @@ +interface ImportMetaEnv { + readonly WXT_CLERK_PUBLISHABLE_KEY?: string; + readonly WXT_CLERK_SYNC_HOST?: string; + readonly WXT_CLERK_FRONTEND_API?: string; + readonly WXT_ORDER_HISTORY_URL?: string; + readonly WXT_EXTENSION_PUBLIC_KEY?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/extension/package.json b/extension/package.json index 5c2caad..21ff300 100644 --- a/extension/package.json +++ b/extension/package.json @@ -13,9 +13,18 @@ "test:run": "vitest run" }, "devDependencies": { + "@clerk/backend": "^3.27.0", "@types/chrome": "^0.1.32", + "@types/react": "^18.3.31", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.7.0", "typescript": "^5.0.0", "vitest": "^3.0.0", "wxt": "^0.20.0" + }, + "dependencies": { + "@clerk/chrome-extension": "^3.1.105", + "react": "^18.3.1", + "react-dom": "^18.3.1" } } diff --git a/extension/scripts/clerk-brand.mjs b/extension/scripts/clerk-brand.mjs new file mode 100644 index 0000000..2ce4555 --- /dev/null +++ b/extension/scripts/clerk-brand.mjs @@ -0,0 +1,141 @@ +/** + * Brand the Clerk instance for Browser Hand and allow this extension origin. + * + * npm run clerk:brand + * npm run clerk:brand -- --dry-run + * + * Reads CLERK_SECRET_KEY, CLERK_EXTENSION_ID, and optional platform credentials + * from the environment or the repo-root .env. Never prints secret values. + */ + +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createClerkClient } from "@clerk/backend"; +import { loadBrowserHandEnv } from "./load-env.mjs"; +import { + BROWSER_HAND_APPLICATION_NAME, + applicationFaviconRequest, + applicationLogoRequest, + applicationNameRequest, + chromeExtensionOrigin, + parseOriginList, + sendPlatformRequest, + syncAllowedOrigins, + ClerkPlatformError, +} from "../account/clerk-setup.ts"; + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(scriptDir, "../.."); +const defaultLogo = path.join(repoRoot, "docs/brand/derived/mark-transparent-512.png"); + +function fail(message) { + console.error(message); + process.exit(1); +} + +function isPlaceholder(value) { + return !value || value.includes("replace_me") || value.includes("your-subdomain"); +} + +async function main() { + loadBrowserHandEnv(repoRoot); + const dryRun = process.argv.includes("--dry-run") || process.env.CLERK_BRAND_DRY_RUN === "1"; + const secretKey = process.env.CLERK_SECRET_KEY?.trim(); + const extensionId = process.env.CLERK_EXTENSION_ID?.trim(); + const applicationId = process.env.CLERK_APPLICATION_ID?.trim(); + const platformKey = process.env.CLERK_PLATFORM_SECRET_KEY?.trim() || secretKey; + const logoPath = process.env.CLERK_LOGO_PATH?.trim() + ? path.resolve(repoRoot, process.env.CLERK_LOGO_PATH.trim()) + : defaultLogo; + + if (isPlaceholder(secretKey)) { + fail("Set CLERK_SECRET_KEY in .env (Clerk Dashboard → API keys). Do not commit it."); + } + if (isPlaceholder(extensionId)) { + fail( + "Set CLERK_EXTENSION_ID to the extension id shown on chrome://extensions (32 letters a-p)." + ); + } + + let extensionOrigin; + try { + extensionOrigin = chromeExtensionOrigin(extensionId); + } catch (error) { + fail(error instanceof Error ? error.message : String(error)); + } + + let extraOrigins = []; + try { + extraOrigins = parseOriginList(process.env.CLERK_EXTRA_ALLOWED_ORIGINS); + } catch (error) { + fail(error instanceof Error ? error.message : String(error)); + } + + const originsToAdd = [extensionOrigin, ...extraOrigins]; + console.log(`Browser Hand Clerk setup${dryRun ? " (dry run)" : ""}`); + console.log(`Extension origin: ${extensionOrigin}`); + console.log(`Origins this run will add: ${originsToAdd.join(", ")}`); + + if (dryRun) { + console.log("Dry run: no Clerk API calls were made."); + if (!applicationId) { + console.log( + "Application name and logo were not changed. Set CLERK_APPLICATION_ID (and CLERK_PLATFORM_SECRET_KEY when the Platform API uses a separate token) to rename the app to Browser Hand and upload the glove mark." + ); + } + return; + } + + const clerk = createClerkClient({ secretKey }); + const allowedOrigins = await syncAllowedOrigins(clerk.instance, originsToAdd); + console.log(`Updated instance allowed origins: ${allowedOrigins.join(", ")}`); + + if (!applicationId || isPlaceholder(applicationId)) { + console.log( + `Instance origins are updated. To set the Clerk application name to "${BROWSER_HAND_APPLICATION_NAME}" and upload the glove logo (emails and Account Portal), set CLERK_APPLICATION_ID and, if required, CLERK_PLATFORM_SECRET_KEY, then run this again. You can also set both in the Clerk Dashboard under Settings → Branding. In-extension components are already themed with the Browser Hand appearance.` + ); + return; + } + + if (!platformKey || isPlaceholder(platformKey)) { + fail("Set CLERK_PLATFORM_SECRET_KEY or CLERK_SECRET_KEY before updating application branding."); + } + + const logoBytes = new Uint8Array(await readFile(logoPath)); + const filename = path.basename(logoPath); + + try { + await sendPlatformRequest(applicationNameRequest(applicationId, platformKey)); + console.log(`Renamed the Clerk application to ${BROWSER_HAND_APPLICATION_NAME}.`); + await sendPlatformRequest( + applicationLogoRequest(applicationId, platformKey, logoBytes, filename) + ); + console.log(`Uploaded ${filename} as the Clerk application logo.`); + } catch (error) { + if (error instanceof ClerkPlatformError && (error.status === 401 || error.status === 403)) { + fail( + `${error.message}\nThe Platform API uses a platform access token. Set CLERK_PLATFORM_SECRET_KEY if it differs from CLERK_SECRET_KEY, or set the name and logo in the Clerk Dashboard (Settings → Branding). Component theming in the extension does not depend on this step.` + ); + } + fail(error instanceof Error ? error.message : String(error)); + } + + try { + await sendPlatformRequest( + applicationFaviconRequest(applicationId, platformKey, logoBytes, filename) + ); + console.log("Uploaded the Clerk application favicon."); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.log(`Favicon was not updated (${message}). The logo upload still stands.`); + } +} + +const isDirectRun = + process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]); +if (isDirectRun) { + main().catch((error) => { + fail(error instanceof Error ? error.message : String(error)); + }); +} diff --git a/extension/scripts/load-env.d.mts b/extension/scripts/load-env.d.mts new file mode 100644 index 0000000..a4505ed --- /dev/null +++ b/extension/scripts/load-env.d.mts @@ -0,0 +1 @@ +export function loadBrowserHandEnv(repoRoot: string): void; diff --git a/extension/scripts/load-env.mjs b/extension/scripts/load-env.mjs new file mode 100644 index 0000000..f5c99d4 --- /dev/null +++ b/extension/scripts/load-env.mjs @@ -0,0 +1,37 @@ +import { existsSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { parseEnv } from "node:util"; + +/** + * Load Browser Hand env files into process.env. + * + * Real environment variables win. Among files, later entries win: + * extension/.env, extension/.env.local, /.env, /.env.local. + * The canonical file is the repo-root `.env` (see `.env.example`). + * + * @param {string} repoRoot + */ +export function loadBrowserHandEnv(repoRoot) { + const locked = new Set(Object.keys(process.env)); + const files = [ + path.join(repoRoot, "extension", ".env"), + path.join(repoRoot, "extension", ".env.local"), + path.join(repoRoot, ".env"), + path.join(repoRoot, ".env.local"), + ]; + + for (const filePath of files) { + if (!existsSync(filePath)) continue; + let parsed; + try { + parsed = parseEnv(readFileSync(filePath, "utf8")); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Could not parse ${filePath}: ${message}`); + } + for (const [key, value] of Object.entries(parsed)) { + if (locked.has(key)) continue; + process.env[key] = value; + } + } +} diff --git a/extension/services/TabManager.ts b/extension/services/TabManager.ts index 6542d7d..ce2ba47 100644 --- a/extension/services/TabManager.ts +++ b/extension/services/TabManager.ts @@ -426,8 +426,6 @@ export class TabManager { return /\bdebugger\b/i.test(message) && /(isn['’]t|is not|not)\s+available/i.test(message); } - private debuggerUnavailableReason: string | null = null; - /** Whether this host browser has reported chrome.debugger as unavailable. */ isDebuggerUnavailable(): boolean { return this.debuggerUnavailableReason !== null; diff --git a/extension/tsconfig.json b/extension/tsconfig.json index 008bc3c..6ad10a0 100644 --- a/extension/tsconfig.json +++ b/extension/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "./.wxt/tsconfig.json" + "extends": "./.wxt/tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx" + } } diff --git a/extension/wxt.config.ts b/extension/wxt.config.ts index a9a00d9..79e979f 100644 --- a/extension/wxt.config.ts +++ b/extension/wxt.config.ts @@ -1,16 +1,80 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import react from "@vitejs/plugin-react"; import { defineConfig } from "wxt"; +import { loadBrowserHandEnv } from "./scripts/load-env.mjs"; + +const extensionDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(extensionDir, ".."); +loadBrowserHandEnv(repoRoot); + +const PUBLIC_ENV_KEYS = [ + "WXT_CLERK_PUBLISHABLE_KEY", + "WXT_CLERK_SYNC_HOST", + "WXT_CLERK_FRONTEND_API", + "WXT_ORDER_HISTORY_URL", +] as const; + +function optionalEnv(name: string): string | undefined { + const value = process.env[name]?.trim(); + return value || undefined; +} + +function hostPermission(value: string | undefined): string | undefined { + if (!value) return undefined; + const withProtocol = value.includes("://") ? value : `https://${value}`; + return `${withProtocol.replace(/\/$/, "")}/*`; +} + +function clientEnvDefines(): Record { + const define: Record = {}; + for (const key of PUBLIC_ENV_KEYS) { + const value = optionalEnv(key); + if (value) define[`import.meta.env.${key}`] = JSON.stringify(value); + } + return define; +} + +const extensionPublicKey = optionalEnv("WXT_EXTENSION_PUBLIC_KEY")?.replace(/\s+/g, ""); +const clerkFrontendHost = hostPermission(optionalEnv("WXT_CLERK_FRONTEND_API")); +const clerkSyncHost = hostPermission(optionalEnv("WXT_CLERK_SYNC_HOST")); export default defineConfig({ // Stable non-hidden path for Chrome → Load unpacked outDir: "dist", + vite: () => ({ + // Repo-root .env is the documented file. Public WXT_* values are also + // copied into import.meta.env below so extension/.env still works. + envDir: repoRoot, + define: clientEnvDefines(), + plugins: [react()], + }), manifest: { name: "Browser Hand", description: "Give agents a hand on your real Chrome — local extension bridge for Browser Hand", // userScripts lets debugger-free mode run on pages whose CSP forbids eval; it // only takes effect once the user turns on "Allow User Scripts" (Chrome) or // the browser grants it (Search). - permissions: ["debugger", "scripting", "tabGroups", "tabs", "storage", "alarms", "userScripts"], - host_permissions: [""], + // storage holds the extension's own Clerk session. cookies is added only when + // WXT_CLERK_SYNC_HOST is set, which is the mode that shares a website session. + permissions: [ + "debugger", + "scripting", + "tabGroups", + "tabs", + "storage", + "alarms", + "userScripts", + ...(clerkSyncHost ? (["cookies"] as const) : []), + ], + host_permissions: [ + "", + ...(clerkFrontendHost ? [clerkFrontendHost] : []), + ...(clerkSyncHost ? [clerkSyncHost] : []), + ], + // Optional. Pins the unpacked extension id so Clerk allowed origins stay stable. + // Public key only — never commit the Chrome Web Store private key. + ...(extensionPublicKey ? { key: extensionPublicKey } : {}), icons: { 16: "icons/icon-16.png", 32: "icons/icon-32.png", diff --git a/package-lock.json b/package-lock.json index 27d89c4..d4ca7ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,8 +39,17 @@ "extension": { "name": "browser-hand-extension", "version": "1.2.0", + "dependencies": { + "@clerk/chrome-extension": "^3.1.105", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, "devDependencies": { + "@clerk/backend": "^3.27.0", "@types/chrome": "^0.1.32", + "@types/react": "^18.3.31", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.7.0", "typescript": "^5.0.0", "vitest": "^3.0.0", "wxt": "^0.20.0" @@ -57,6 +66,12 @@ "many-keys-map": "^3.0.0" } }, + "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/@aklinker1/rollup-plugin-visualizer": { "version": "5.12.0", "resolved": "https://registry.npmjs.org/@aklinker1/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", @@ -145,7 +160,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", @@ -160,37 +174,218 @@ "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/@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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.13.tgz", + "integrity": "sha512-j1nvOQCZYSgp7HLmisnCXFTpVvFODiijQ9QHkORfGKWH6YDtskTEeCJS7eV545GZ9UBzrq0e2xsOd5QQ5PjIqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.13", + "@babel/helper-compilation-targets": "^7.29.13", + "@babel/helper-module-transforms": "^7.29.13", + "@babel/helpers": "^7.29.13", + "@babel/parser": "^7.29.13", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.13", + "@babel/types": "^7.29.11", + "@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/core/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==", "dev": true, "license": "MIT" }, - "node_modules/@babel/helper-string-parser": { + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz", + "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.13.tgz", + "integrity": "sha512-T5FRo+RzVHvGjWOuF1Ljc44b+NXAOazodHkGUHp4I3zCkQ4/sNVJm56h1S278LMR0bY5xBhztGziSbli8zObIw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.13", + "@babel/types": "^7.29.11", + "@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-compilation-targets": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.13.tgz", + "integrity": "sha512-8WxJ4/Vmes3ghacj8VOJy3NW8eyHiFY/4thU/LSBuBE470+igjR/j1ZTNn8fV5VdNrLx6iXUwig526UdOvfP0w==", + "dev": true, + "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-compilation-targets/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz", + "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { "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==", + "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-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.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.13.tgz", + "integrity": "sha512-/U96wOz+SCoZ7Da/8kjDGltcNY280JPQcRNPK0Hk3PTbdpaiBDQImtC6JsbVwb1HGMr3usZQU2WsWVjhYNmRzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.13" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.13.tgz", + "integrity": "sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ==", + "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==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { + "node_modules/@babel/helper-validator-option": { "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==", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.13.tgz", + "integrity": "sha512-laoRpD/kO/iA4CDlgJbixHekg7oPvWALK0PQyK3Ob2I2njZRvVAD77RfWCIImtqWCq6KijoBoZK7bp/HlTWASQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.13.tgz", + "integrity": "sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.11" }, "bin": { "parser": "bin/babel-parser.js" @@ -199,28 +394,558 @@ "node": ">=6.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/runtime": { "version": "7.28.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", - "dev": true, "license": "MIT", "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==", - "dev": true, + "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.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.13.tgz", + "integrity": "sha512-0ve0xMlFA73dgY1oyJRY6RoW+/bncdcOQqg6jjYcVfXBK90OdLXjm9R6lmRetdZVrPjXkKIoMVBqYFPjcAlZ4Q==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.13", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.13", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.11", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz", + "integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.11", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@base-org/account": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@base-org/account/-/account-2.0.1.tgz", + "integrity": "sha512-tySVNx+vd6XEynZL0uvB10uKiwnAfThr8AbKTwILVG86mPbLAhEOInQIk+uDnvpTvfdUhC1Bi5T/46JvFoLZQQ==", + "license": "Apache-2.0", + "dependencies": { + "@noble/hashes": "1.4.0", + "clsx": "1.2.1", + "eventemitter3": "5.0.1", + "idb-keyval": "6.2.1", + "ox": "0.6.9", + "preact": "10.24.2", + "viem": "^2.31.7", + "zustand": "5.0.3" + } + }, + "node_modules/@base-org/account/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/@base-org/account/node_modules/@noble/curves/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/@base-org/account/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/@base-org/account/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/@base-org/account/node_modules/viem": { + "version": "2.57.16", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.57.16.tgz", + "integrity": "sha512-+4uaUw3NfkqjaqsZp8Zci94K33oc9rv7mIUgvPduJxsY/tI0ASi2YkaXoUVSBGfdIw/QPWuVnIf9LTFf35IoMg==", + "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.59", + "ws": "8.21.0" + }, + "peerDependencies": { + "mppx": ">=0.17.0", + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "mppx": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@base-org/account/node_modules/viem/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/@base-org/account/node_modules/viem/node_modules/ox": { + "version": "0.14.59", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.59.tgz", + "integrity": "sha512-Y+DPEQScTEGKteQATjtytE1JRQ5cg1R2+DvYQ/lbhUCm/xS9n0Jnkkf2AqQyIntefQxs8xY6dZ+kyNZ7zf2FzA==", + "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/@base-org/account/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/@clerk/backend": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-3.27.0.tgz", + "integrity": "sha512-pmhVVP9EsAOL1rlRU4xR0XSL1BJczxGAJIqv8c+C9fRz7TYzCndGUMSSE/Zg7i2QOA24ExJbEJUiOjILdUL2EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clerk/shared": "^4.44.1", + "standardwebhooks": "^1.0.0" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/@clerk/chrome-extension": { + "version": "3.1.105", + "resolved": "https://registry.npmjs.org/@clerk/chrome-extension/-/chrome-extension-3.1.105.tgz", + "integrity": "sha512-oW16pI8ZRNnKfk9P9yBFQ5shx5NuvYN2feU8kr+Yi7sS51FybqzMk/7gDJGiEwJngvdO6rguM5lOK0xZuYoIgQ==", + "license": "MIT", + "dependencies": { + "@clerk/clerk-js": "^6.42.3", + "@clerk/react": "^6.18.6", + "@clerk/shared": "^4.44.1", + "@clerk/ui": "^1.42.4", + "webextension-polyfill": "~0.12.0" + }, + "engines": { + "node": ">=20.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + } + }, + "node_modules/@clerk/clerk-js": { + "version": "6.42.3", + "resolved": "https://registry.npmjs.org/@clerk/clerk-js/-/clerk-js-6.42.3.tgz", + "integrity": "sha512-hjUf0j4Q6mWSEKC5Mg5WuE1uNu5eYPF8NpMJvGCPAxx7GwJKOTb1Pct2FNybEGf4mC14Xau+ic3jk75GB12joA==", + "license": "MIT", + "dependencies": { + "@base-org/account": "2.0.1", + "@clerk/shared": "^4.44.1", + "@coinbase/wallet-sdk": "4.3.7", + "@stripe/stripe-js": "5.6.0", + "@swc/helpers": "0.5.21", + "@tanstack/query-core": "^5.101.4", + "@wallet-standard/core": "1.1.1", + "@zxcvbn-ts/core": "3.0.4", + "@zxcvbn-ts/language-common": "3.0.4", + "alien-signals": "2.0.6", + "browser-tabs-lock": "1.3.0", + "core-js": "3.47.0", + "crypto-js": "^4.2.0", + "dequal": "2.0.3" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/@clerk/localizations": { + "version": "4.24.6", + "resolved": "https://registry.npmjs.org/@clerk/localizations/-/localizations-4.24.6.tgz", + "integrity": "sha512-U9WKVMI3a2K8jCKKE0iVECV43haDAyQ/vNFWx61Ry5JZhh6icceujDe4RAfjp1pqEA5JJ46xGdO5XrjXu5sCpg==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^4.44.1" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/@clerk/react": { + "version": "6.18.6", + "resolved": "https://registry.npmjs.org/@clerk/react/-/react-6.18.6.tgz", + "integrity": "sha512-vNsg5smbANI7Bf8W+2e6p4hR3LlMZvSKT+lih8tVlBZVAQzkoVCgmD7EmiEwMqLz23y8Z0klDBtejQlcpn9Deg==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^4.44.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + } + }, + "node_modules/@clerk/shared": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-4.44.1.tgz", + "integrity": "sha512-c06U6GtlLJNOKj900rHDSqjVRz0/0zb3RZXVGKUqTulfQxr4Ym543JE6cbOnZVJZCbm0fGPhdRKvHq6fO/SvVw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "^5.101.4", + "dequal": "2.0.3", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.8" + }, + "engines": { + "node": ">=20.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/ui": { + "version": "1.42.4", + "resolved": "https://registry.npmjs.org/@clerk/ui/-/ui-1.42.4.tgz", + "integrity": "sha512-oUnF0mVCaWskOlCgVEjz14E9vxf/74+YW5BDNcgFwhZxljgqzgC8dD08dGsNxbv9lMf5X/xdGBs2+X//U4UvBg==", + "license": "MIT", + "dependencies": { + "@clerk/localizations": "^4.24.6", + "@clerk/shared": "^4.44.1", + "@emotion/cache": "11.11.0", + "@emotion/react": "11.11.1", + "@floating-ui/react": "0.27.20", + "@formkit/auto-animate": "^0.8.2", + "@swc/helpers": "0.5.21", + "@wallet-standard/core": "1.1.1", + "copy-to-clipboard": "3.3.3", + "core-js": "3.47.0", + "csstype": "3.1.3", + "dequal": "2.0.3", + "input-otp": "1.4.2", + "qrcode.react": "4.2.0" + }, + "engines": { + "node": ">=20.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + } + }, + "node_modules/@clerk/ui/node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/@coinbase/wallet-sdk": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-4.3.7.tgz", + "integrity": "sha512-z6e5XDw6EF06RqkeyEa+qD0dZ2ZbLci99vx3zwDY//XO8X7166tqKJrR2XlQnzVmtcUuJtCd5fCvr9Cu6zzX7w==", + "license": "Apache-2.0", + "dependencies": { + "@noble/hashes": "^1.4.0", + "clsx": "^1.2.1", + "eventemitter3": "^5.0.1", + "preact": "^10.24.2", + "viem": "^2.27.2" + } + }, + "node_modules/@coinbase/wallet-sdk/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/@coinbase/wallet-sdk/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/@coinbase/wallet-sdk/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/@coinbase/wallet-sdk/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/@coinbase/wallet-sdk/node_modules/ox": { + "version": "0.14.59", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.59.tgz", + "integrity": "sha512-Y+DPEQScTEGKteQATjtytE1JRQ5cg1R2+DvYQ/lbhUCm/xS9n0Jnkkf2AqQyIntefQxs8xY6dZ+kyNZ7zf2FzA==", + "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/@coinbase/wallet-sdk/node_modules/viem": { + "version": "2.57.16", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.57.16.tgz", + "integrity": "sha512-+4uaUw3NfkqjaqsZp8Zci94K33oc9rv7mIUgvPduJxsY/tI0ASi2YkaXoUVSBGfdIw/QPWuVnIf9LTFf35IoMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" + "@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.59", + "ws": "8.21.0" + }, + "peerDependencies": { + "mppx": ">=0.17.0", + "typescript": ">=5.0.4" }, + "peerDependenciesMeta": { + "mppx": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@coinbase/wallet-sdk/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": ">=6.9.0" + "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/@devicefarmer/adbkit": { @@ -293,6 +1018,153 @@ } } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/serialize/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -735,6 +1607,65 @@ "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": { + "version": "0.27.20", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.20.tgz", + "integrity": "sha512-CMqMy7OaXl9W0eq1Uy7L7i2Y/anPvHmFmESd2CEw0t5YvZhcVCeo4MBevAmswRllX7Y2dEidA4ozGPunLSTQpw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.9", + "@floating-ui/utils": "^0.2.12", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "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/@formkit/auto-animate": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.8.4.tgz", + "integrity": "sha512-DHHC01EJ1p70Q0z/ZFRBIY8NDnmfKccQoyoM84Tgb6omLMat6jivCdf272Y8k3nf4Lzdin/Y4R9q8uFtU0GbnA==", + "license": "MIT" + }, "node_modules/@hono/node-server": { "version": "1.19.17", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz", @@ -809,7 +1740,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -831,7 +1761,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -841,20 +1770,69 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "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==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "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.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/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/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -921,6 +1899,13 @@ "node": ">=18" } }, + "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.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.3.tgz", @@ -1013,9 +1998,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1030,9 +2012,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1047,9 +2026,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1064,9 +2040,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1081,9 +2054,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1098,9 +2068,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1115,9 +2082,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1132,9 +2096,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1149,9 +2110,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1166,9 +2124,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1183,9 +2138,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1200,9 +2152,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1217,9 +2166,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1305,16 +2251,156 @@ ], "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT" + "optional": true, + "os": [ + "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/bip32/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/@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/@scure/bip39/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/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stripe/stripe-js": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-5.6.0.tgz", + "integrity": "sha512-w8CEY73X/7tw2KKlL3iOk679V9bWseE4GzNz3zlaYxcTjmcmWOathRb0emgo/QQ3eoNzmq68+2Y2gxluAv3xGw==", + "license": "MIT", + "engines": { + "node": ">=12.16" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.107.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.107.1.tgz", + "integrity": "sha512-PSyAik11oQBEVOiVy5hjZWHmoUqOXqK1WYy4v55mcxozEfKaQgpUMsgnXhWjuESzSht8JfzqMS1m6M/9CpNgvg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, + "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/chai": { "version": "5.2.3", @@ -1393,6 +2479,40 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/webextension-polyfill": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.12.5.tgz", @@ -1410,6 +2530,27 @@ "@types/node": "*" } }, + "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/@vitest/expect": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", @@ -1525,6 +2666,83 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@wallet-standard/app": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/app/-/app-1.1.1.tgz", + "integrity": "sha512-WDGwoByhP5gwHH01r5EaLgQdLVkACPCdOMQhmhn8rsm10h/siSgTorShzBxrn0ExSPof+Lu+C3TfgqBrPa1xoQ==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@wallet-standard/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/base/-/base-1.1.1.tgz", + "integrity": "sha512-gggIHTtxicF9XFMQ12DkfS6NAG92Ak795JeSA7f2whAQ6Y3AkMWWuCMxSZXG2NIPN42kEaZSNVjqMsJRaJRxMQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=22" + } + }, + "node_modules/@wallet-standard/core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/core/-/core-1.1.1.tgz", + "integrity": "sha512-5Xmjc6+Oe0hcPfVc5n8F77NVLwx1JVAoCVgQpLyv/43/bhtIif+Gx3WUrDlaSDoM8i2kA2xd6YoFbHCxs+e0zA==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/app": "^1.1.0", + "@wallet-standard/base": "^1.1.0", + "@wallet-standard/errors": "^0.1.1", + "@wallet-standard/features": "^1.1.0", + "@wallet-standard/wallet": "^1.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@wallet-standard/errors": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@wallet-standard/errors/-/errors-0.1.2.tgz", + "integrity": "sha512-oEzKUqJefKby6wcIvaJgrSEe/uNn/rnqkJ0P/85K+h0i5Tdo9E3L22VWq/j5K1e8hHMnZd6LgaIr8m/Wn7X/Ng==", + "license": "Apache-2.0", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@wallet-standard/features": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/features/-/features-1.1.1.tgz", + "integrity": "sha512-aCWYmVeSCGViyEU5k7GMoW8zxE4Gs+C1s1Pp2XLesvSNlnZ4PMES9HUnTB3hl0b3RVj7C61yze3IWyrncqg4MA==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@wallet-standard/wallet": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/wallet/-/wallet-1.1.1.tgz", + "integrity": "sha512-8WiRPaKk/wNNRZhB2eVhpR/JW7/aqTCMoZhgVUCujuzDmxxmGvsosMxdCG4NAdYkoyozAHCX8/xLtlWUn5mNdQ==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.1" + }, + "engines": { + "node": ">=22" + } + }, "node_modules/@webext-core/fake-browser": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@webext-core/fake-browser/-/fake-browser-1.5.2.tgz", @@ -1596,6 +2814,42 @@ "@types/har-format": "*" } }, + "node_modules/@zxcvbn-ts/core": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@zxcvbn-ts/core/-/core-3.0.4.tgz", + "integrity": "sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw==", + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "1.0.16" + } + }, + "node_modules/@zxcvbn-ts/language-common": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@zxcvbn-ts/language-common/-/language-common-3.0.4.tgz", + "integrity": "sha512-viSNNnRYtc7ULXzxrQIVUNwHAPSXRtoIwy/Tq4XQQdIknBzw4vz36lQLF6mvhMlTIlpjoN/Z1GFu/fwiAlUSsw==", + "license": "MIT" + }, + "node_modules/abitype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.3.0.tgz", + "integrity": "sha512-fk6Te+bojIFrMvMZrnOO+SxCB+RUksTGOzq/60ZRvs1L+BVzvi2bqt9L3W/17ZLdZsyM1FuYf65P5nlmoiH1Bg==", + "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/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -1628,6 +2882,12 @@ "node": ">= 14" } }, + "node_modules/alien-signals": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-2.0.6.tgz", + "integrity": "sha512-P3TxJSe31bUHBiblg59oU1PpaWPtmxF9GhJ/cB7OkgJ0qN/ifFSKUI25/v8ZhsT+lIG6ac8DpTOplXxORX6F3Q==", + "license": "MIT" + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -1829,6 +3089,21 @@ } } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1916,6 +3191,19 @@ "bare-path": "^3.0.0" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.35", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.35.tgz", + "integrity": "sha512-GNk8KX52CHqeNKGk8+7tZnQab/2k1t/BgrUHPKNdijzpEhF1saPQZHVKb6m5loSAxB39gyrTYw34Qf9CQ3u1qQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/basic-ftp": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", @@ -2023,6 +3311,50 @@ "resolved": "relay", "link": true }, + "node_modules/browser-tabs-lock": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.3.0.tgz", + "integrity": "sha512-g6nHaobTiT0eMZ7jh16YpD2kcjAp+PInbiVq3M1x6KKaEIVhT4v9oURNIpZLOZ3LQbQ3XYfNhMAb/9hzNLIWrw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "lodash": ">=4.17.21" + } + }, + "node_modules/browserslist": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.5.tgz", + "integrity": "sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==", + "dev": true, + "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.11.33", + "caniuse-lite": "^1.0.30001819", + "electron-to-chromium": "^1.5.464", + "node-releases": "^2.0.60", + "update-browserslist-db": "^1.3.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -2101,6 +3433,15 @@ "node": ">=8" } }, + "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/camelcase": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", @@ -2114,6 +3455,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001823", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001823.tgz", + "integrity": "sha512-TQ5s/djDjNk1axreUW+4rULH+BmdCMLiJkhaXnO5wqivG7tITkSUj4ItIdzqdZHKaXcHAV8dhH3JxOM7/7bwqw==", + "dev": true, + "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/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -2135,7 +3497,6 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -2433,6 +3794,15 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2451,6 +3821,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2528,6 +3907,32 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-js": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -2535,6 +3940,68 @@ "dev": true, "license": "MIT" }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/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/cosmiconfig/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/cosmiconfig/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/cosmiconfig/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "deprecated": "Active development of CryptoJS has been discontinued. This library is no longer maintained.", + "license": "MIT" + }, "node_modules/css-select": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-7.0.0.tgz", @@ -2577,6 +4044,12 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "MIT" + }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", @@ -2698,7 +4171,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2869,6 +4341,13 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.478", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.478.tgz", + "integrity": "sha512-pftG+ma5Lm8O6/p45bhYT6StBN5ziQymaRzmCZwFl8DwWHKPPD93FdOmP1aqftcepolQYvrcyTLa17kE8Yf+jg==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "10.6.0", "dev": true, @@ -2911,12 +4390,20 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, + "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-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -3149,6 +4636,22 @@ "node": ">=6" } }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -3168,6 +4671,12 @@ "node": ">= 10.8.0" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/firefox-profile": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/firefox-profile/-/firefox-profile-4.7.0.tgz", @@ -3237,6 +4746,15 @@ "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/fx-runner": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.4.0.tgz", @@ -3265,7 +4783,17 @@ "graceful-readlink": ">= 1.0.0" }, "engines": { - "node": ">= 0.6.x" + "node": ">= 0.6.x" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, "node_modules/get-caller-file": { @@ -3340,7 +4868,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/global-directory": { @@ -3390,6 +4917,27 @@ "dev": true, "license": "MIT" }, + "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/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/hono": { "version": "4.12.32", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz", @@ -3544,6 +5092,12 @@ "node": ">= 14" } }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==", + "license": "Apache-2.0" + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -3551,6 +5105,22 @@ "dev": true, "license": "MIT" }, + "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/import-meta-resolve": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", @@ -3579,6 +5149,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/input-otp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/ip-address": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", @@ -3605,9 +5185,23 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, "license": "MIT" }, + "node_modules/is-core-module": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.18.0.tgz", + "integrity": "sha512-qyax9fgaKOEC4qK8WZn7Q4IcoZC9RC1O1fFxRcRTq/oMMkWT/uXpqpXa12kzWR409zAt1drYN9N9BO26UmBCmw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -3808,6 +5402,21 @@ "node": ">=0.10.0" } }, + "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", @@ -3818,6 +5427,12 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", @@ -3825,6 +5440,18 @@ "dev": true, "license": "MIT" }, + "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": "3.0.2", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", @@ -4033,6 +5660,12 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -4107,6 +5740,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/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/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -4402,6 +6053,16 @@ "node": ">= 8" } }, + "node_modules/node-releases": { + "version": "2.0.63", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.63.tgz", + "integrity": "sha512-6li2QanLsAXpIH2R6j77MzRyvOyrvdTqlKtO4CgBdHGNvN99ZZfyxszHc5o5+IZBWD5u6Hzwg83JoGXmqZNG9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4539,6 +6200,53 @@ "node": ">= 0.4.0" } }, + "node_modules/ox": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", + "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/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/ox/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/pac-proxy-agent": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", @@ -4597,6 +6305,18 @@ "dev": true, "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", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", @@ -4617,6 +6337,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -4651,7 +6386,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -4789,6 +6523,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/preact": { + "version": "10.24.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.2.tgz", + "integrity": "sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -4984,6 +6728,15 @@ "node": ">=18" } }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/quansync": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", @@ -5077,6 +6830,47 @@ "destr": "^2.0.5" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "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/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -5162,6 +6956,36 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.14", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.14.tgz", + "integrity": "sha512-1ZjYvR9sbkiwC5+9QHetqW5HmA2pHRMaoupD01t2HlYnV/vydC2zPwLixhsMieSWVZt0JJRg5LVIk0Ow1D/TrA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.18.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", "dev": true, @@ -5281,6 +7105,15 @@ "node": ">=11.0.0" } }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/scule": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", @@ -5511,6 +7344,17 @@ "dev": true, "license": "MIT" }, + "node_modules/standardwebhooks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz", + "integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", @@ -5633,6 +7477,30 @@ "dev": true, "license": "MIT" }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tabbable": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.1.tgz", + "integrity": "sha512-dMCuwDUY6Noz3zVKLefBC4wq+aJV20yc7vWmM7uTNGwtQSFF5kX7OUIUsxuVajz/bdt5Xqdah3/0NLjth+cM5A==", + "license": "MIT" + }, "node_modules/tar-fs": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", @@ -5796,6 +7664,12 @@ "node": ">=14.14" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -5832,6 +7706,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -5864,7 +7739,7 @@ }, "node_modules/typescript": { "version": "5.9.3", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -6054,6 +7929,37 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "dev": true, + "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/update-notifier": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz", @@ -6380,6 +8286,12 @@ "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", "license": "Apache-2.0" }, + "node_modules/webextension-polyfill": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz", + "integrity": "sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==", + "license": "MPL-2.0" + }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", @@ -7162,6 +9074,31 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "17.7.3", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", @@ -7264,12 +9201,41 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true, + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "relay": { "name": "browser-hand-relay", "version": "0.1.0", diff --git a/package.json b/package.json index 8a1fa4c..a79ecc3 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "setup": "bash scripts/setup.sh", "browser-hand": "node cli-js/src/cli.js", "doctor": "node cli-js/src/cli.js doctor", + "clerk:brand": "node extension/scripts/clerk-brand.mjs", "relay": "node cli-js/src/cli.js relay", "smoke:live": "bash scripts/smoke-live.sh", "gym:compare": "node cli-js/src/gym-compare.js",