From 44967e52e94a37c2b5a8ce1499401798805f80d3 Mon Sep 17 00:00:00 2001 From: Simple Analytics Codex Date: Tue, 6 Oct 2026 11:15:24 +0200 Subject: [PATCH 1/3] Import commissioned Webflow app source Requested by Adriaan: copy the contractor delivery from runclubs/Simple-Analytics-App into the Simple Analytics repository before completing the Webflow release. Preserve all delivered files from 05c638ff6a9c6c1455a8819ea8cfdd04136933bc unchanged. Refs #2; release work tracked in #1. --- README.md | 111 + data-client/.env.example | 14 + data-client/.eslintrc.json | 3 + data-client/.gitignore | 46 + data-client/README.md | 36 + data-client/app/api/auth/authorize/route.ts | 37 + data-client/app/api/auth/callback/route.ts | 127 + data-client/app/api/auth/token/route.ts | 103 + data-client/app/api/health/route.ts | 47 + data-client/app/api/scripts/apply/route.ts | 48 + data-client/app/api/scripts/register/route.ts | 43 + data-client/app/api/scripts/remove/route.ts | 41 + data-client/app/api/scripts/status/route.ts | 41 + data-client/app/api/sites/route.ts | 41 + data-client/app/favicon.ico | Bin 0 -> 25931 bytes data-client/app/layout.tsx | 18 + .../app/lib/controllers/scriptController.ts | 151 + data-client/app/lib/utils/database.ts | 135 + data-client/app/lib/utils/jwt.ts | 145 + data-client/app/lib/utils/ngrokManager.ts | 105 + data-client/app/lib/utils/scriptGenerator.ts | 100 + data-client/app/route.ts | 5 + data-client/next.config.mjs | 50 + data-client/package.json | 29 + data-client/public/authComplete.html | 16 + data-client/scripts/dev.ts | 47 + data-client/scripts/tunnel.ts | 12 + data-client/tsconfig.json | 33 + data-client/tsconfig.server.json | 14 + designer-extension/.env.development | 1 + designer-extension/.env.prod | 1 + designer-extension/.gitignore | 24 + designer-extension/README.md | 50 + designer-extension/bundle.zip | Bin 0 -> 66731 bytes designer-extension/eslint.config.js | 28 + designer-extension/index.html | 16 + designer-extension/package-lock.json | 3376 +++++++++++++++++ designer-extension/package.json | 35 + designer-extension/public/vite.svg | 1 + designer-extension/src/App.css | 442 +++ designer-extension/src/App.tsx | 49 + .../src/components/AuthScreen.tsx | 66 + .../src/components/ConfigPanel.tsx | 511 +++ .../src/components/StatusMessage.tsx | 13 + designer-extension/src/hooks/useAuth.ts | 213 ++ .../src/hooks/useInstallScript.ts | 140 + designer-extension/src/hooks/useSites.ts | 20 + designer-extension/src/main.tsx | 30 + designer-extension/src/services/api.ts | 96 + designer-extension/src/types/types.ts | 65 + designer-extension/src/vite-env.d.ts | 1 + designer-extension/tsconfig.app.json | 25 + designer-extension/tsconfig.json | 7 + designer-extension/tsconfig.node.json | 22 + designer-extension/vite.config.ts | 104 + designer-extension/webflow.json | 6 + icon-chevron.svg | 3 + package-lock.json | 309 ++ package.json | 14 + 59 files changed, 7266 insertions(+) create mode 100644 README.md create mode 100644 data-client/.env.example create mode 100644 data-client/.eslintrc.json create mode 100644 data-client/.gitignore create mode 100644 data-client/README.md create mode 100644 data-client/app/api/auth/authorize/route.ts create mode 100644 data-client/app/api/auth/callback/route.ts create mode 100644 data-client/app/api/auth/token/route.ts create mode 100644 data-client/app/api/health/route.ts create mode 100644 data-client/app/api/scripts/apply/route.ts create mode 100644 data-client/app/api/scripts/register/route.ts create mode 100644 data-client/app/api/scripts/remove/route.ts create mode 100644 data-client/app/api/scripts/status/route.ts create mode 100644 data-client/app/api/sites/route.ts create mode 100644 data-client/app/favicon.ico create mode 100644 data-client/app/layout.tsx create mode 100644 data-client/app/lib/controllers/scriptController.ts create mode 100644 data-client/app/lib/utils/database.ts create mode 100644 data-client/app/lib/utils/jwt.ts create mode 100644 data-client/app/lib/utils/ngrokManager.ts create mode 100644 data-client/app/lib/utils/scriptGenerator.ts create mode 100644 data-client/app/route.ts create mode 100644 data-client/next.config.mjs create mode 100644 data-client/package.json create mode 100644 data-client/public/authComplete.html create mode 100644 data-client/scripts/dev.ts create mode 100644 data-client/scripts/tunnel.ts create mode 100644 data-client/tsconfig.json create mode 100644 data-client/tsconfig.server.json create mode 100644 designer-extension/.env.development create mode 100644 designer-extension/.env.prod create mode 100644 designer-extension/.gitignore create mode 100644 designer-extension/README.md create mode 100644 designer-extension/bundle.zip create mode 100644 designer-extension/eslint.config.js create mode 100644 designer-extension/index.html create mode 100644 designer-extension/package-lock.json create mode 100644 designer-extension/package.json create mode 100644 designer-extension/public/vite.svg create mode 100644 designer-extension/src/App.css create mode 100644 designer-extension/src/App.tsx create mode 100644 designer-extension/src/components/AuthScreen.tsx create mode 100644 designer-extension/src/components/ConfigPanel.tsx create mode 100644 designer-extension/src/components/StatusMessage.tsx create mode 100644 designer-extension/src/hooks/useAuth.ts create mode 100644 designer-extension/src/hooks/useInstallScript.ts create mode 100644 designer-extension/src/hooks/useSites.ts create mode 100644 designer-extension/src/main.tsx create mode 100644 designer-extension/src/services/api.ts create mode 100644 designer-extension/src/types/types.ts create mode 100644 designer-extension/src/vite-env.d.ts create mode 100644 designer-extension/tsconfig.app.json create mode 100644 designer-extension/tsconfig.json create mode 100644 designer-extension/tsconfig.node.json create mode 100644 designer-extension/vite.config.ts create mode 100644 designer-extension/webflow.json create mode 100644 icon-chevron.svg create mode 100644 package-lock.json create mode 100644 package.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..d27fddb --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# Simple Analytics — Webflow Hybrid App + +A Webflow Hybrid App that lets site owners add [Simple Analytics](https://www.simpleanalytics.com/) to their Webflow sites directly from the Webflow Designer. It uses the Webflow Data API to inject the Simple Analytics tracking script via Custom Code, and the Designer API to authenticate users. + +## Architecture + +``` +designer-extension/ Vite + React frontend (runs inside Webflow Designer) + ├── src/App.tsx Entry point — auth gate + config panel + ├── src/hooks/useAuth.ts OAuth token exchange via ID tokens + └── src/services/api.ts Calls to the data-client API + +data-client/ Next.js backend (API + OAuth server) + ├── app/api/auth/ OAuth authorize / callback / token routes + ├── app/api/scripts/ Register + apply Simple Analytics script + └── app/lib/utils/ Database (SQLite), JWT, ngrok helper +``` + +## Prerequisites + +- Node.js 18+ +- A [Webflow](https://webflow.com) site and workspace +- A registered Webflow App (see Setup step 2) + +## Setup + +1. **Clone the repository:** + + ```bash + git clone https://github.com/runclubs/Simple-Analytics-App.git + cd Simple-Analytics-App + ``` + +2. **Install all dependencies:** + + ```bash + npm install + npm run install:all + ``` + + This installs the root dependencies (concurrently), plus all packages for both `data-client/` and `designer-extension/`. + +3. **Register a Webflow App** in [your Workspace settings](https://developers.webflow.com/v2.0.0/data/docs/register-an-app): + + - Redirect URI: `http://localhost:3001/api/auth/callback` + - Required scopes (all four must be enabled): + - `authorized_user:read` + - `sites:read` + - `custom_code:read` + - `custom_code:write` + + > **Note:** if you forget to enable `authorized_user:read`, the OAuth flow will fail with an `invalid_scope` error. + +4. **Create the environment file:** + + ```bash + cp data-client/.env.example data-client/.env + ``` + + Open `data-client/.env` and fill in your credentials from the Webflow Dashboard (Workspace Settings → Apps & Integrations → Your App): + + | Variable | Required | Where to find it | + |---|---|---| + | `WEBFLOW_CLIENT_ID` | Yes | Your app's OAuth Client ID | + | `WEBFLOW_CLIENT_SECRET` | Yes | Your app's OAuth Client Secret | + | `NGROK_AUTH_TOKEN` | No | Only needed for external testing | + + The remaining variables (`PORT`, `DESIGNER_EXTENSION_URI`) have sensible defaults and can be left as-is. + + > **Important:** the `.env` file contains secrets and is git-ignored. The `.env.example` file in the repo serves as a reference for which variables are needed. Never commit actual API keys. + +5. **Start the app:** + + ```bash + npm run dev + ``` + + This starts both servers concurrently: + - **Data Client (backend):** `http://localhost:3001` + - **Designer Extension (frontend):** `http://localhost:1337` + +6. **Authorize the app** by navigating to `http://localhost:3001` in your browser — this redirects to the Webflow OAuth consent screen. Click **Authorize**. + +7. **Open the Designer Extension** in the Webflow Designer: Apps panel → your app → **"Launch Development App"**. + +## Environment Variables + +| Variable | Required | Description | +|---|---|---| +| `WEBFLOW_CLIENT_ID` | Yes | OAuth client ID from Webflow | +| `WEBFLOW_CLIENT_SECRET` | Yes | OAuth client secret from Webflow | +| `NGROK_AUTH_TOKEN` | No | For exposing localhost during development | +| `PORT` | No | Data client port (default: `3001`) | +| `DESIGNER_EXTENSION_URI` | No | Extension origin for CORS + postMessage (default: `http://localhost:1337`) | + +## Security + +- **Tokens encrypted at rest** — access tokens are AES-256-GCM encrypted in the SQLite database, keyed from `WEBFLOW_CLIENT_SECRET`. +- **Env validation** — the server refuses to start if required credentials are missing. +- **CORS** — the API only accepts requests from the configured `DESIGNER_EXTENSION_URI`. +- **Security headers** — `X-Content-Type-Options: nosniff` and `X-Frame-Options: DENY` on all API responses. +- **postMessage origin** — the OAuth callback targets the extension origin instead of `*`. + +## Tech Stack + +- **Data Client:** [Next.js](https://nextjs.org/), [Webflow SDK](https://github.com/webflow/js-webflow-api), SQLite +- **Designer Extension:** [React](https://react.dev/), [Vite](https://vitejs.dev/), [Webflow Designer API](https://developers.webflow.com/designer/reference/introduction) + +## License + +MIT diff --git a/data-client/.env.example b/data-client/.env.example new file mode 100644 index 0000000..486602c --- /dev/null +++ b/data-client/.env.example @@ -0,0 +1,14 @@ +WEBFLOW_CLIENT_ID= +WEBFLOW_CLIENT_SECRET= + +# Upstash Redis (created via Vercel Marketplace integration) +KV_REST_API_URL= +KV_REST_API_TOKEN= + +# Production: Webflow extension origin for CORS +# DESIGNER_EXTENSION_URI=https://698f529c469b4d47089ac992.webflow-ext.com + +# Development only (not needed on Vercel) +NGROK_AUTH_TOKEN= +PORT=3001 +DESIGNER_EXTENSION_URI=http://localhost:1337 diff --git a/data-client/.eslintrc.json b/data-client/.eslintrc.json new file mode 100644 index 0000000..3722418 --- /dev/null +++ b/data-client/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript"] +} diff --git a/data-client/.gitignore b/data-client/.gitignore new file mode 100644 index 0000000..4e0917b --- /dev/null +++ b/data-client/.gitignore @@ -0,0 +1,46 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for committing if needed) +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# lock files +package-lock.json + +# database +.db diff --git a/data-client/README.md b/data-client/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/data-client/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/data-client/app/api/auth/authorize/route.ts b/data-client/app/api/auth/authorize/route.ts new file mode 100644 index 0000000..ef67b1a --- /dev/null +++ b/data-client/app/api/auth/authorize/route.ts @@ -0,0 +1,37 @@ +import { WebflowClient } from "webflow-api"; +import { NextResponse } from "next/server"; +import { OauthScope } from "webflow-api/api/types/OAuthScope"; + +export const dynamic = "force-dynamic"; + +/** + * Authorize API Route Handler + * -------------------------- + * Returns the Webflow OAuth URL as JSON when ?json=true is set (for popup flows), + * otherwise redirects directly (for direct navigation). + */ + +const scopes = [ + "sites:read", + "custom_code:read", + "custom_code:write", + "authorized_user:read", +]; + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const isDesigner = searchParams.get("state") === "webflow_designer"; + + const authorizeUrl = WebflowClient.authorizeURL({ + scope: scopes as OauthScope[], + clientId: process.env.WEBFLOW_CLIENT_ID!, + state: isDesigner ? "webflow_designer" : undefined, + }); + + // Return URL as JSON for client-side navigation (avoids bot detection on server redirect) + if (searchParams.get("json") === "true") { + return NextResponse.json({ url: authorizeUrl }); + } + + return NextResponse.redirect(authorizeUrl); +} diff --git a/data-client/app/api/auth/callback/route.ts b/data-client/app/api/auth/callback/route.ts new file mode 100644 index 0000000..b106b4e --- /dev/null +++ b/data-client/app/api/auth/callback/route.ts @@ -0,0 +1,127 @@ +import { WebflowClient } from "webflow-api"; +import { NextRequest, NextResponse } from "next/server"; +import db from "../../../lib/utils/database"; + +export const dynamic = "force-dynamic"; + +/** + * Callback API Route Handler + * ------------------------- + * This route processes the OAuth callback from Webflow after a user authorizes the application. + * + * Flow: + * 1. Receives authorization code from Webflow + * 2. Exchanges code for access token + * 3. Retrieves user's Webflow sites + * 4. Stores site authorization details + * 5. Handles response based on access method (popup vs direct) + * + * @param {NextRequest} request - The incoming request object containing: + * - searchParams: URL parameters including the authorization 'code' + * - headers: Request headers to determine if accessed via popup + * + * @returns {Promise} + * - For popup windows: Returns HTML to close window and notify parent + * - For direct navigation: Redirects to the first authorized Webflow site + * - On error: Returns 400 status if no code provided + * + * @requires {WEBFLOW_CLIENT_ID} - Environment variable for OAuth client ID + * @requires {WEBFLOW_CLIENT_SECRET} - Environment variable for OAuth client secret + */ +export async function GET(request: NextRequest) { + // Get the authorization code from the request + const searchParams = request.nextUrl.searchParams; + const code = searchParams.get("code"); + + // If no code, return a 400 error + if (!code) { + return NextResponse.json({ error: "No code provided" }, { status: 400 }); + } + + try { + // Get Access Token + const accessToken = await WebflowClient.getAccessToken({ + clientId: process.env.WEBFLOW_CLIENT_ID!, + clientSecret: process.env.WEBFLOW_CLIENT_SECRET!, + code: code, + }); + + // Instantiate the Webflow Client + const webflow = new WebflowClient({ accessToken }); + + // Get Site ID to pair with the access token + const sites = await webflow.sites.list(); + const authInfo = await webflow.token.introspect(); + + // Store site authorizations in parallel + const siteList = sites?.sites ?? []; + if (siteList.length > 0) { + await Promise.all( + siteList.map((site) => db.insertSiteAuthorization(site.id, accessToken)) + ); + } + + // Check if the authorization request came from our Webflow designer extension + const isAppPopup = searchParams.get("state") === "webflow_designer"; + console.log("isAppPopup", isAppPopup); + + // If the request is from a popup window, return HTML to close the window + const extensionOrigin = process.env.DESIGNER_EXTENSION_URI || "*"; + if (isAppPopup) { + return new NextResponse( + ` + + + Authorization Complete + + +

Authorization complete! This window will close automatically.

+ + + `, + { + headers: { + "Content-Type": "text/html", + }, + } + ); + } else { + // If authorized to the Workspace - redirect to the Dashboard + const workspaceIds = + authInfo?.authorization?.authorizedTo?.workspaceIds ?? []; + if (workspaceIds.length > 0) { + return NextResponse.redirect( + `https://webflow.com/dashboard?workspace=${workspaceIds[0]}` + ); + } else { + // If authorized to the Site - redirect to the Designer Extension + const firstSite = siteList[0]; + if (firstSite) { + return NextResponse.redirect( + `https://${firstSite.shortName}.design.webflow.com?app=${process.env.WEBFLOW_CLIENT_ID}` + ); + } + } + } + } catch (error) { + console.error("Error in callback:", error); + return NextResponse.json( + { error: "Failed to process authorization" }, + { status: 500 } + ); + } +} diff --git a/data-client/app/api/auth/token/route.ts b/data-client/app/api/auth/token/route.ts new file mode 100644 index 0000000..4be83bf --- /dev/null +++ b/data-client/app/api/auth/token/route.ts @@ -0,0 +1,103 @@ +import { NextRequest, NextResponse } from "next/server"; +import jwt from "../../../lib/utils/jwt"; +import db from "../../../lib/utils/database"; + +export const dynamic = "force-dynamic"; + +/* + Token Exchange API Route + ---------------------- + This route handles user authentication by exchanging a Webflow ID token for a custom session token. + + Flow: + 1. Designer Extension (client) sends a POST request with: + - Site ID + - Webflow ID token + + 2. Server performs these steps: + a) Retrieves an Access Token using the Site ID + - This Access Token proves that the site the request is coming from is authorized to access Webflow's API + + b) Verifies the User's ID Token with Webflow + - Sends the ID token to Webflow's API, making an authenticated request with the retrieved Access Token + - Receives user information if the token is valid + + c) Creates a database record in the UserAuth table + - Stores the User ID and Access Token for future requests + + d) Issues a session token + - Creates a JWT containing user information + - Includes an expiration time for security + + 3. Returns to client: + - Session token + - Expiration timestamp + + Error Handling: + - Returns 401 if Site ID is invalid or unauthorized + - Returns 401 if user verification fails +*/ +export async function POST(request: NextRequest) { + // Clone the request since we need to read the body twice + const clonedRequest = request.clone() as NextRequest; + + // Get the Access Token for the Site ID + const accessToken = await jwt.getAccessToken(clonedRequest); + + // If the Access Token is not found, return a 401 Unauthorized response + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + try { + // Get the ID Token from the request body + const body = await request.json(); + const token = body.idToken; + + // Verify the ID Token with Webflow's API + const response = await fetch("https://api.webflow.com/beta/token/resolve", { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, // This Access Token proves the site the request is coming from is authorized to access Webflow's API + }, + body: JSON.stringify({ + idToken: token, + }), + }); + + // Validate the response from Webflow + if (!response.ok) { + console.error("Webflow token resolve failed:", response.status); + return NextResponse.json( + { error: "Failed to verify ID token with Webflow" }, + { status: 401 } + ); + } + + // Get the user information from the response + const user = await response.json(); + console.log("User:", user); + + // Generate a Session Token + const tokenPayload = await jwt.createSessionToken(user); + const sessionToken = tokenPayload.sessionToken; + const expAt = tokenPayload.exp; + + // Store the User ID and Access Token in the database + await db.insertUserAuthorization(user.id, accessToken); + + // Return the Session Token and Expiration Time to the Designer Extension (client) + return NextResponse.json({ sessionToken, exp: expAt }); + } catch (e) { + // If the user is not associated with the site, return a 401 Unauthorized response + console.error("Unauthorized user", e); + return NextResponse.json( + { + error: "Error: User is not associated with authorization for this site", + }, + { status: 401 } + ); + } +} diff --git a/data-client/app/api/health/route.ts b/data-client/app/api/health/route.ts new file mode 100644 index 0000000..c47c9ff --- /dev/null +++ b/data-client/app/api/health/route.ts @@ -0,0 +1,47 @@ +import { NextResponse } from "next/server"; +import { Redis } from "@upstash/redis"; + +export const dynamic = "force-dynamic"; + +/** + * GET /api/health + * + * Quick health check that tests Redis connectivity and env var presence. + */ +export async function GET() { + const checks: Record = {}; + + // Check env vars (existence only, not values) + checks.WEBFLOW_CLIENT_ID = process.env.WEBFLOW_CLIENT_ID ? "set" : "MISSING"; + checks.WEBFLOW_CLIENT_SECRET = process.env.WEBFLOW_CLIENT_SECRET ? "set" : "MISSING"; + checks.KV_REST_API_URL = process.env.KV_REST_API_URL ? "set" : "MISSING"; + checks.KV_REST_API_TOKEN = process.env.KV_REST_API_TOKEN ? "set" : "MISSING"; + checks.DESIGNER_EXTENSION_URI = process.env.DESIGNER_EXTENSION_URI || "not set (using default)"; + + // Test Redis connection + try { + if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) { + const redis = new Redis({ + url: process.env.KV_REST_API_URL, + token: process.env.KV_REST_API_TOKEN, + }); + await redis.ping(); + checks.redis_connection = "OK"; + } else { + checks.redis_connection = "SKIPPED (missing credentials)"; + } + } catch (error) { + checks.redis_connection = `FAILED: ${error instanceof Error ? error.message : String(error)}`; + } + + const allGood = checks.WEBFLOW_CLIENT_ID === "set" && + checks.WEBFLOW_CLIENT_SECRET === "set" && + checks.KV_REST_API_URL === "set" && + checks.KV_REST_API_TOKEN === "set" && + checks.redis_connection === "OK"; + + return NextResponse.json({ + status: allGood ? "healthy" : "unhealthy", + checks, + }); +} diff --git a/data-client/app/api/scripts/apply/route.ts b/data-client/app/api/scripts/apply/route.ts new file mode 100644 index 0000000..c48912e --- /dev/null +++ b/data-client/app/api/scripts/apply/route.ts @@ -0,0 +1,48 @@ +import { NextRequest, NextResponse } from "next/server"; +import { WebflowClient } from "webflow-api"; +import jwt from "../../../lib/utils/jwt"; +import { applySiteScript } from "../../../lib/controllers/scriptController"; + +export const dynamic = "force-dynamic"; + +/** + * POST /api/scripts/apply + * + * Accepts { siteId, scriptId, version }. + * Applies the registered script to the site header via upsertCustomCode. + */ +export async function POST(request: NextRequest) { + const clonedRequest = request.clone() as NextRequest; + const accessToken = await jwt.verifyAuth(clonedRequest); + + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + try { + const body = await request.json(); + const { siteId, scriptId, version } = body as { + siteId: string; + scriptId: string; + version: string; + }; + + if (!siteId || !scriptId || !version) { + return NextResponse.json( + { error: "siteId, scriptId, and version are required" }, + { status: 400 } + ); + } + + const webflow = new WebflowClient({ accessToken }); + await applySiteScript(webflow, siteId, scriptId, version); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error in /api/scripts/apply:", error); + return NextResponse.json( + { error: error instanceof Error ? error.message : "Failed to apply script" }, + { status: 500 } + ); + } +} diff --git a/data-client/app/api/scripts/register/route.ts b/data-client/app/api/scripts/register/route.ts new file mode 100644 index 0000000..b7d20a8 --- /dev/null +++ b/data-client/app/api/scripts/register/route.ts @@ -0,0 +1,43 @@ +import { NextRequest, NextResponse } from "next/server"; +import { WebflowClient } from "webflow-api"; +import jwt from "../../../lib/utils/jwt"; +import { generateScript, ScriptConfig } from "../../../lib/utils/scriptGenerator"; +import { registerInlineScript } from "../../../lib/controllers/scriptController"; + +export const dynamic = "force-dynamic"; + +/** + * POST /api/scripts/register + * + * Accepts { siteId, config } where config has trackPageviews, trackEvents, collectDNT. + * Generates the inline script and registers it with Webflow. + */ +export async function POST(request: NextRequest) { + const clonedRequest = request.clone() as NextRequest; + const accessToken = await jwt.verifyAuth(clonedRequest); + + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + try { + const body = await request.json(); + const { siteId, config } = body as { siteId: string; config: ScriptConfig }; + + if (!siteId) { + return NextResponse.json({ error: "siteId is required" }, { status: 400 }); + } + + const sourceCode = generateScript(config); + const webflow = new WebflowClient({ accessToken }); + const result = await registerInlineScript(webflow, siteId, sourceCode); + + return NextResponse.json(result); + } catch (error) { + console.error("Error in /api/scripts/register:", error); + return NextResponse.json( + { error: error instanceof Error ? error.message : "Failed to register script" }, + { status: 500 } + ); + } +} diff --git a/data-client/app/api/scripts/remove/route.ts b/data-client/app/api/scripts/remove/route.ts new file mode 100644 index 0000000..57e3733 --- /dev/null +++ b/data-client/app/api/scripts/remove/route.ts @@ -0,0 +1,41 @@ +import { NextRequest, NextResponse } from "next/server"; +import { WebflowClient } from "webflow-api"; +import jwt from "../../../lib/utils/jwt"; +import { removeSiteScript } from "../../../lib/controllers/scriptController"; + +export const dynamic = "force-dynamic"; + +/** + * POST /api/scripts/remove + * + * Accepts { siteId }. + * Removes all scripts applied by our app from the site. + */ +export async function POST(request: NextRequest) { + const clonedRequest = request.clone() as NextRequest; + const accessToken = await jwt.verifyAuth(clonedRequest); + + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + try { + const body = await request.json(); + const { siteId } = body as { siteId: string }; + + if (!siteId) { + return NextResponse.json({ error: "siteId is required" }, { status: 400 }); + } + + const webflow = new WebflowClient({ accessToken }); + await removeSiteScript(webflow, siteId); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error in /api/scripts/remove:", error); + return NextResponse.json( + { error: error instanceof Error ? error.message : "Failed to remove script" }, + { status: 500 } + ); + } +} diff --git a/data-client/app/api/scripts/status/route.ts b/data-client/app/api/scripts/status/route.ts new file mode 100644 index 0000000..30d66e4 --- /dev/null +++ b/data-client/app/api/scripts/status/route.ts @@ -0,0 +1,41 @@ +import { NextRequest, NextResponse } from "next/server"; +import { WebflowClient } from "webflow-api"; +import jwt from "../../../lib/utils/jwt"; +import { checkScriptStatus } from "../../../lib/controllers/scriptController"; + +export const dynamic = "force-dynamic"; + +/** + * GET /api/scripts/status?siteId=... + * + * Checks if a Simple Analytics script is already registered for the site. + * Returns { installed: boolean, version?: string } + */ +export async function GET(request: NextRequest) { + const clonedRequest = request.clone() as NextRequest; + const accessToken = await jwt.verifyAuth(clonedRequest); + + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + try { + const { searchParams } = new URL(request.url); + const siteId = searchParams.get("siteId"); + + if (!siteId) { + return NextResponse.json({ error: "siteId is required" }, { status: 400 }); + } + + const webflow = new WebflowClient({ accessToken }); + const status = await checkScriptStatus(webflow, siteId); + + return NextResponse.json(status); + } catch (error) { + console.error("Error in /api/scripts/status:", error); + return NextResponse.json( + { error: error instanceof Error ? error.message : "Failed to check script status" }, + { status: 500 } + ); + } +} diff --git a/data-client/app/api/sites/route.ts b/data-client/app/api/sites/route.ts new file mode 100644 index 0000000..f35d766 --- /dev/null +++ b/data-client/app/api/sites/route.ts @@ -0,0 +1,41 @@ +import { NextRequest, NextResponse } from "next/server"; +import { WebflowClient } from "webflow-api"; +import jwt from "../../lib/utils/jwt"; + +export const dynamic = "force-dynamic"; + +/* + Sites API Route + --------------- + This route handles the GET request from the client to retrieve the list of sites associated with the user. +*/ +export async function GET(request: NextRequest) { + try { + // Clone the request since we need to read the body twice + const clonedRequest = request.clone() as NextRequest; + + // Verify the user is authenticated + const accessToken = await jwt.verifyAuth(clonedRequest); + + // If the user is not authenticated, return a 401 Unauthorized response + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Create a new WebflowClient with the Access Token + const webflow = new WebflowClient({ accessToken }); + + // Get the list of sites associated with the user + const data = await webflow.sites.list(); + + // Return the list of sites to the client + return NextResponse.json({ data }); + } catch (error) { + // If an error occurs, return a 500 Internal Server Error response + console.error("Error handling authenticated request:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} diff --git a/data-client/app/favicon.ico b/data-client/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/data-client/app/layout.tsx b/data-client/app/layout.tsx new file mode 100644 index 0000000..a564f19 --- /dev/null +++ b/data-client/app/layout.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Simple Analytics - Data Client", + description: "Backend for Simple Analytics Webflow Hybrid App", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/data-client/app/lib/controllers/scriptController.ts b/data-client/app/lib/controllers/scriptController.ts new file mode 100644 index 0000000..bd38ede --- /dev/null +++ b/data-client/app/lib/controllers/scriptController.ts @@ -0,0 +1,151 @@ +import { WebflowClient } from "webflow-api"; + +const SCRIPT_DISPLAY_NAME = "Simple Analytics"; + +/** + * Increment a semver version string (e.g., "1.0.0" -> "1.0.1"). + */ +function incrementVersion(version: string): string { + const parts = version.split(".").map(Number); + parts[2] = (parts[2] || 0) + 1; + return parts.join("."); +} + +interface RegisterResult { + id: string; + version: string; + updated: boolean; +} + +/** + * Register an inline script for a site. + * If a script with the same display name already exists, auto-increments the version. + */ +export async function registerInlineScript( + webflow: WebflowClient, + siteId: string, + sourceCode: string +): Promise { + // Check for existing scripts with the same name + const existingScripts = await webflow.scripts.list(siteId); + const matching = (existingScripts.registeredScripts || []) + .filter((s) => s.displayName === SCRIPT_DISPLAY_NAME) + .sort((a, b) => + (b.version ?? "0.0.0").localeCompare(a.version ?? "0.0.0", undefined, { + numeric: true, + }) + ); + + let newVersion = "1.0.0"; + let isUpdate = false; + + if (matching.length > 0) { + const latest = matching[0]; + newVersion = incrementVersion(latest.version ?? "1.0.0"); + isUpdate = true; + } + + const script = await webflow.scripts.registerInline(siteId, { + displayName: SCRIPT_DISPLAY_NAME, + sourceCode, + version: newVersion, + canCopy: true, + }); + + return { + id: script.id!, + version: newVersion, + updated: isUpdate, + }; +} + +/** + * Apply a registered script to a site's header via upsertCustomCode. + */ +export async function applySiteScript( + webflow: WebflowClient, + siteId: string, + scriptId: string, + version: string +): Promise { + await webflow.sites.scripts.upsertCustomCode(siteId, { + scripts: [ + { + id: scriptId, + location: "header", + version, + }, + ], + }); +} + +interface ScriptStatus { + installed: boolean; + version?: string; + duplicateDetected: boolean; +} + +/** + * Check if a Simple Analytics script is already registered for a site, + * and detect duplicate SA scripts (e.g. manually added via Project Settings). + */ +export async function checkScriptStatus( + webflow: WebflowClient, + siteId: string +): Promise { + // Check registered scripts from our app + const existingScripts = await webflow.scripts.list(siteId); + const matching = (existingScripts.registeredScripts || []) + .filter((s) => s.displayName === SCRIPT_DISPLAY_NAME) + .sort((a, b) => + (b.version ?? "0.0.0").localeCompare(a.version ?? "0.0.0", undefined, { + numeric: true, + }) + ); + + const installed = matching.length > 0; + const version = installed ? (matching[0].version ?? "1.0.0") : undefined; + + // Check for duplicate SA scripts on the site (e.g. manually added) + let duplicateDetected = false; + try { + const customCode = await webflow.sites.scripts.getCustomCode(siteId); + const scripts = customCode?.scripts || []; + + // Count how many applied scripts reference simpleanalyticscdn in their source + // Our app registers scripts with displayName "Simple Analytics", + // so any OTHER script pointing to simpleanalyticscdn.com is a duplicate + const ourScriptIds = new Set(matching.map((s) => s.id)); + const otherSAScripts = scripts.filter((s: { id?: string }) => { + // If this script is one of ours, skip it + if (s.id && ourScriptIds.has(s.id)) return false; + + // Check registered scripts list for source URL + const registered = (existingScripts.registeredScripts || []).find( + (r) => r.id === s.id + ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const reg = registered as Record | undefined; + if (typeof reg?.sourceUrl === "string" && reg.sourceUrl.includes("simpleanalyticscdn.com")) return true; + if (typeof reg?.source === "string" && reg.source.includes("simpleanalyticscdn.com")) return true; + + return false; + }); + + duplicateDetected = otherSAScripts.length > 0; + } catch { + // getCustomCode may fail if no custom code exists yet — that's fine + } + + return { installed, version, duplicateDetected }; +} + +/** + * Remove all scripts applied by our app from the site. + */ +export async function removeSiteScript( + webflow: WebflowClient, + siteId: string +): Promise { + await webflow.sites.scripts.deleteCustomCode(siteId); +} diff --git a/data-client/app/lib/utils/database.ts b/data-client/app/lib/utils/database.ts new file mode 100644 index 0000000..7f580c8 --- /dev/null +++ b/data-client/app/lib/utils/database.ts @@ -0,0 +1,135 @@ +import { Redis } from "@upstash/redis"; +import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from "crypto"; + +/** + * Database Utility (Upstash Redis) + * -------------------------------- + * This module provides functions to interact with Upstash Redis for token storage. + * Tokens are encrypted at rest using AES-256-GCM derived from WEBFLOW_CLIENT_SECRET. + * + * Key structure: + * site: → encrypted access token + * user: → encrypted access token + */ + +const ENCRYPTION_ALGORITHM = "aes-256-gcm"; + +function deriveKey(): Buffer { + const secret = process.env.WEBFLOW_CLIENT_SECRET; + if (!secret) { + throw new Error("WEBFLOW_CLIENT_SECRET is required for token encryption"); + } + return scryptSync(secret, "simple-analytics-salt", 32); +} + +function encrypt(plaintext: string): string { + const key = deriveKey(); + const iv = randomBytes(12); + const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv); + const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]); + const authTag = cipher.getAuthTag(); + return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`; +} + +function decrypt(encoded: string): string { + const key = deriveKey(); + const [ivHex, authTagHex, ciphertextHex] = encoded.split(":"); + const iv = Buffer.from(ivHex, "hex"); + const authTag = Buffer.from(authTagHex, "hex"); + const ciphertext = Buffer.from(ciphertextHex, "hex"); + const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv); + decipher.setAuthTag(authTag); + return decipher.update(ciphertext) + decipher.final("utf8"); +} + +/** Singleton Redis client */ +let redis: Redis | null = null; + +function getRedis(): Redis { + if (!redis) { + redis = new Redis({ + url: process.env.KV_REST_API_URL!, + token: process.env.KV_REST_API_TOKEN!, + }); + } + return redis; +} + +/** + * Stores a site authorization (siteId → accessToken). + */ +export async function insertSiteAuthorization( + siteId: string, + accessToken: string +) { + const db = getRedis(); + const encryptedToken = encrypt(accessToken); + await db.set(`site:${siteId}`, encryptedToken); + console.log("Site authorization pairing updated."); +} + +/** + * Stores a user authorization (userId → accessToken). + */ +export async function insertUserAuthorization( + userId: string, + accessToken: string +) { + const db = getRedis(); + const encryptedToken = encrypt(accessToken); + await db.set(`user:${userId}`, encryptedToken); + console.log("User access token pairing updated."); +} + +/** + * Retrieves the access token for a given site ID. + */ +export async function getAccessTokenFromSiteId( + siteId: string +): Promise { + const db = getRedis(); + const encryptedToken = await db.get(`site:${siteId}`); + + if (!encryptedToken) { + throw new Error("No access token found or site does not exist"); + } + + return decrypt(encryptedToken); +} + +/** + * Retrieves the access token for a given user ID. + */ +export async function getAccessTokenFromUserId( + userId: string +): Promise { + const db = getRedis(); + const encryptedToken = await db.get(`user:${userId}`); + + if (!encryptedToken) { + throw new Error("No access token found or user does not exist"); + } + + return decrypt(encryptedToken); +} + +/** + * Clears all data from the database. + * Note: In production, this uses FLUSHDB which removes ALL keys. + * Use with caution. + */ +export async function clearDatabase() { + const db = getRedis(); + await db.flushdb(); + console.log("Database cleared successfully"); +} + +const database = { + getAccessTokenFromSiteId, + getAccessTokenFromUserId, + insertSiteAuthorization, + insertUserAuthorization, + clearDatabase, +}; + +export default database; diff --git a/data-client/app/lib/utils/jwt.ts b/data-client/app/lib/utils/jwt.ts new file mode 100644 index 0000000..81d659c --- /dev/null +++ b/data-client/app/lib/utils/jwt.ts @@ -0,0 +1,145 @@ +import { SignJWT, jwtVerify } from "jose"; +import { NextRequest } from "next/server"; +import db from "./database"; + +/* + JWT Utility Functions + --------------------- + This file provides a comprehensive set of utilities for handling JSON Web Tokens (JWT) + in a Next.js application. It manages authentication between Webflow's Designer Extension + and our backend API. + + Architecture: + - Uses JOSE library for JWT operations + - Implements HS256 algorithm for token signing + - Stores access tokens in database + - Handles token verification and extraction + + Security: + - Tokens expire after 24 hours + - Uses WEBFLOW_CLIENT_SECRET for signing + - Validates signatures on every request + - Safely handles verification failures +*/ + +interface User { + id: string; + email: string; +} + +interface JWTPayload { + user: User; +} + +/** + * Creates a signed JWT session token for authenticated users + * + * @param user - Object containing user ID and email + * @returns Promise containing: + * - sessionToken: Signed JWT token + * - exp: Token expiration timestamp + * + * Flow: + * 1. Encodes signing secret + * 2. Creates and signs token with 24h expiration + * 3. Verifies token and extracts expiration + */ +const createSessionToken = async (user: User) => { + // Encode secret + const secret = new TextEncoder().encode(process.env.WEBFLOW_CLIENT_SECRET); + + // Create session token + const sessionToken = await new SignJWT({ user }) + .setProtectedHeader({ alg: "HS256" }) + .setExpirationTime("24h") + .sign(secret); + + // Decode token + const decodedToken = await jwtVerify(sessionToken, secret); + + return { + sessionToken, + exp: decodedToken.payload.exp, + }; +}; + +/** + * Validates JWT token from request and retrieves associated access token + * + * @param request - Incoming Next.js request + * @returns Promise - Access token if valid, null otherwise + * + * Flow: + * 1. Extracts Bearer token from Authorization header + * 2. Verifies token signature and expiration + * 3. Retrieves associated access token from database + * 4. Returns null if any step fails + */ +const verifyAuth = async (request: NextRequest) => { + const authHeader = request.headers.get("authorization"); // Get authorization header from request + const sessionToken = authHeader?.split(" ")[1]; // Extract the token from 'Bearer ' + + // If no session token, return null + if (!sessionToken) { + return null; + } + + try { + // Verify session token + const secret = new TextEncoder().encode(process.env.WEBFLOW_CLIENT_SECRET); + const { payload } = (await jwtVerify(sessionToken, secret)) as { + payload: JWTPayload; + }; + + // Get user ID from payload + const userId = payload.user.id; + + // Get access token from user ID + const accessToken = await db.getAccessTokenFromUserId(userId as string); + + // Return access token + return accessToken; + } catch { + // If error, return null + return null; + } +}; + +/** + * Retrieves Webflow access token for a specific site + * + * @param request - Incoming request containing siteId in body + * @returns Promise - Access token if found, null otherwise + * + * Flow: + * 1. Extracts siteId from request body + * 2. Looks up access token in database + * 3. Handles errors and missing tokens + */ +const getAccessToken = async (request: NextRequest) => { + try { + const body = await request.json(); + const { siteId } = body; + + // Get the access token from your database or wherever you store it + const accessToken = await db.getAccessTokenFromSiteId(siteId); + + if (!accessToken) { + console.error(`No access token found for site ${siteId}`); + return null; + } + + return accessToken; + } catch (error) { + console.error("Error getting access token:", error); + return null; + } +}; + +const jwtUtils = { + createSessionToken, + verifyAuth, + getAccessToken, +}; + +export default jwtUtils; diff --git a/data-client/app/lib/utils/ngrokManager.ts b/data-client/app/lib/utils/ngrokManager.ts new file mode 100644 index 0000000..ea59015 --- /dev/null +++ b/data-client/app/lib/utils/ngrokManager.ts @@ -0,0 +1,105 @@ +import ngrok from "@ngrok/ngrok"; +import chalk from "chalk"; + +/** + * Ngrok Utility Functions + * ------------------------ + * This file provides utility functions for managing ngrok tunnels. + */ + +/** + * Draws a box around the content with a border. + * + * @param {string} content - The content to be displayed inside the box. + * @returns {string} The content wrapped in a box border. + */ +function drawBox(content: string): string { + const lines = content.split("\n"); + const width = Math.max(...lines.map((line) => stripAnsi(line).length)); + + const top = `╭${"─".repeat(width + 2)}╮`; + const bottom = `╰${"─".repeat(width + 2)}╯`; + + const paddedLines = lines.map((line) => { + const visibleLength = stripAnsi(line).length; + const padding = " ".repeat(width - visibleLength); + return `│ ${line}${padding} │`; + }); + + return [ + "", + chalk.blue(top), + ...paddedLines.map((line) => chalk.blue(line)), + chalk.blue(bottom), + "", + ].join("\n"); +} + +/** + * Strips ANSI color codes from a string to calculate its length. + * + * @param {string} string - The string to strip ANSI color codes from. + * @returns {string} The string with ANSI color codes removed. + */ +function stripAnsi(string: string): string { + return string.replace(/\u001b\[[0-9]{1,2}m/g, ""); +} + +export async function startNgrok(port: number): Promise { + console.log(chalk.yellow("⚡ Starting ngrok tunnel...")); + + try { + const listener = await ngrok.connect({ + addr: port, + authtoken: process.env.NGROK_AUTH_TOKEN, + }); + + const url = listener.url(); + if (!url) throw new Error("Failed to get ngrok URL"); + + console.log(chalk.green("✓ Ngrok tunnel started successfully")); + return url; + } catch (error) { + console.error(chalk.red("✗ Ngrok failed to start:"), error); + throw error; + } +} + +/** + * Sets up the development environment by starting an ngrok tunnel and displaying URLs. + * + * @returns {Promise} The URL of the ngrok tunnel. + */ +export async function setupDevEnvironment(): Promise { + try { + const PORT = parseInt(process.env.PORT || "3000"); + const localUrl = `http://localhost:${PORT}`; + const ngrokUrl = process.env.NGROK_AUTH_TOKEN + ? await startNgrok(PORT) + : null; + + const baseUrl = ngrokUrl + ? `${ngrokUrl}/api/auth/callback` + : `${localUrl}/api/auth/callback`; + const message = [ + chalk.bold("Development Environment"), + "", + `${chalk.bold("Local URL: ")}${chalk.cyan(localUrl)}`, + `${chalk.bold("Callback: ")}${chalk.cyan(`${baseUrl}`)}`, + "", + `${chalk.yellow("⚠")} ${chalk.bold( + "Update your Webflow callback URL to:" + )}`, + chalk.cyan(`${baseUrl}`), + ].join("\n"); + + console.log(drawBox(message)); + return baseUrl; + } catch (error) { + console.error( + chalk.red("Error setting up development environment:"), + error + ); + throw error; + } +} diff --git a/data-client/app/lib/utils/scriptGenerator.ts b/data-client/app/lib/utils/scriptGenerator.ts new file mode 100644 index 0000000..0b52368 --- /dev/null +++ b/data-client/app/lib/utils/scriptGenerator.ts @@ -0,0 +1,100 @@ +export interface ScriptConfig { + trackPageviews: boolean; + trackEvents: boolean; + collectDNT: boolean; + automatedEvents: boolean; + hashMode: boolean; + ignorePages: string; + customDomain: string; + overwriteDomain: string; + collectOutbound: boolean; + collectEmails: boolean; + collectDownloads: boolean; + downloadExtensions: string; + usePageTitle: boolean; + collectFullUrls: boolean; + collectLinkEvents: boolean; +} + +/** + * Get the base CDN URL, using a custom domain if provided. + */ +function getBaseUrl(config: ScriptConfig): string { + if (config.customDomain) { + return `https://${config.customDomain}`; + } + return "https://scripts.simpleanalyticscdn.com"; +} + +/** + * Generate an inline Simple Analytics script based on configuration options. + * + * Ported from the original Simple Analytics Webflow app (public/index.js). + */ +export function generateScript(config: ScriptConfig): string { + const baseUrl = getBaseUrl(config); + let code = "(function(){"; + + if (config.trackEvents || config.collectLinkEvents) { + code += + 'window.sa_event=window.sa_event||function(){var a=[].slice.call(arguments);window.sa_event.q?window.sa_event.q.push(a):window.sa_event.q=[a]};'; + } + + code += 'var s=document.createElement("script");'; + code += `s.src="${baseUrl}/latest.js";`; + code += "s.async=true;"; + + if (!config.trackPageviews) { + code += 's.dataset.autoCollect="false";'; + } + if (config.collectDNT) { + code += 's.dataset.collectDnt="true";'; + } + if (config.hashMode) { + code += 's.dataset.mode="hash";'; + } + if (config.ignorePages) { + code += `s.dataset.ignorePages="${config.ignorePages}";`; + } + if (config.overwriteDomain) { + code += `s.dataset.hostname="${config.overwriteDomain}";`; + } + + code += "document.head.appendChild(s);"; + + if (config.automatedEvents) { + code += 'var ae=document.createElement("script");'; + code += `ae.src="${baseUrl}/auto-events.js";`; + code += "ae.async=true;"; + + // Build data-collect value from individual toggles + const collectTypes: string[] = []; + if (config.collectOutbound) collectTypes.push("outbound"); + if (config.collectEmails) collectTypes.push("emails"); + if (config.collectDownloads) collectTypes.push("downloads"); + if (collectTypes.length > 0 && collectTypes.length < 3) { + code += `ae.dataset.collect="${collectTypes.join(",")}";`; + } + + if (config.downloadExtensions) { + code += `ae.dataset.extensions="${config.downloadExtensions}";`; + } + if (!config.usePageTitle) { + code += 'ae.dataset.useTitle="false";'; + } + if (config.collectFullUrls) { + code += 'ae.dataset.fullUrls="true";'; + } + + code += "document.head.appendChild(ae);"; + } + + if (config.collectLinkEvents) { + code += + 'function saLoadedLinkEvents(){document.querySelectorAll("a[data-sa-link-event]").forEach(function(e){var h=e.getAttribute("href"),n=e.getAttribute("data-sa-link-event");if(h&&window.sa_event&&window.sa_loaded){e.addEventListener("click",function(ev){if(e.getAttribute("target")==="_blank"){ev.preventDefault();window.sa_event(n,function(){window.open(h,"_blank")})}else{ev.preventDefault();window.sa_event(n,function(){window.location.href=h})}})}})}if(document.readyState==="ready"||document.readyState==="complete"){saLoadedLinkEvents()}else{document.addEventListener("readystatechange",function(ev){if(ev.target.readyState==="complete")saLoadedLinkEvents()})}'; + } + + code += "})();"; + + return code; +} diff --git a/data-client/app/route.ts b/data-client/app/route.ts new file mode 100644 index 0000000..76df2c8 --- /dev/null +++ b/data-client/app/route.ts @@ -0,0 +1,5 @@ +import { redirect } from 'next/navigation'; + +export async function GET() { + return redirect('/api/auth/authorize'); +} \ No newline at end of file diff --git a/data-client/next.config.mjs b/data-client/next.config.mjs new file mode 100644 index 0000000..11c9506 --- /dev/null +++ b/data-client/next.config.mjs @@ -0,0 +1,50 @@ +const nextConfig = { + async headers() { + // In production, Webflow designer extensions are served from various + // Webflow-controlled origins. We allow all origins for the API routes + // since authentication is handled via JWT tokens, not CORS. + const allowedOrigin = process.env.DESIGNER_EXTENSION_URI || "*"; + + return [ + { + source: "/api/:path*", + headers: [ + { key: "Access-Control-Allow-Credentials", value: "true" }, + { + key: "Access-Control-Allow-Origin", + value: allowedOrigin, + }, + { + key: "Access-Control-Allow-Methods", + value: "GET, POST, PUT, DELETE, OPTIONS", + }, + { + key: "Access-Control-Allow-Headers", + value: + "X-CSRF-Token, X-Requested-With, Authorization, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version", + }, + { key: "X-Content-Type-Options", value: "nosniff" }, + { key: "X-Frame-Options", value: "DENY" }, + ], + }, + ]; + }, + + async rewrites() { + return [ + { + source: "/api/:path*", + destination: "/api/:path*", + has: [ + { + type: "header", + key: "Origin", + value: "(?.*)", + }, + ], + }, + ]; + }, +}; + +export default nextConfig; diff --git a/data-client/package.json b/data-client/package.json new file mode 100644 index 0000000..420ef71 --- /dev/null +++ b/data-client/package.json @@ -0,0 +1,29 @@ +{ + "name": "simple-analytics-data-client", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "tsx scripts/dev.ts", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "@ngrok/ngrok": "^1.4.1", + "@upstash/redis": "^1.36.2", + "chalk": "^5.3.0", + "jose": "^5.2.1", + "next": "^14.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "webflow-api": "^3.1.0" + }, + "devDependencies": { + "@types/node": "^20.11.16", + "@types/react": "^18.2.55", + "@types/react-dom": "^18.2.19", + "eslint": "^8.56.0", + "eslint-config-next": "^14.1.0", + "tsx": "^4.19.2", + "typescript": "^5.3.3" + } +} diff --git a/data-client/public/authComplete.html b/data-client/public/authComplete.html new file mode 100644 index 0000000..9a5acee --- /dev/null +++ b/data-client/public/authComplete.html @@ -0,0 +1,16 @@ + + + +

Authentication successful! You can now close this window.

+ + + + \ No newline at end of file diff --git a/data-client/scripts/dev.ts b/data-client/scripts/dev.ts new file mode 100644 index 0000000..b6d6fd6 --- /dev/null +++ b/data-client/scripts/dev.ts @@ -0,0 +1,47 @@ +import { createServer, IncomingMessage, ServerResponse } from "http"; +import next from "next"; +import { setupDevEnvironment } from "../app/lib/utils/ngrokManager"; + +function validateEnv() { + const required = ["WEBFLOW_CLIENT_ID", "WEBFLOW_CLIENT_SECRET"]; + const missing = required.filter((key) => !process.env[key]); + if (missing.length > 0) { + console.error( + `Missing required environment variables: ${missing.join(", ")}\n` + + "Copy .env.example to .env and fill in your Webflow app credentials." + ); + process.exit(1); + } +} + +async function dev() { + const app = next({ dev: true }); + const handle = app.getRequestHandler(); + + await app.prepare(); + + validateEnv(); + + const server = createServer( + async (req: IncomingMessage, res: ServerResponse) => { + try { + await handle(req, res); + } catch (err) { + console.error("Error handling request:", err); + res.statusCode = 500; + res.end("Internal Server Error"); + } + } + ); + + const port = parseInt(process.env.PORT || "3001"); + server.listen(port, async () => { + console.log(`> Ready on http://localhost:${port}`); + await setupDevEnvironment(); + }); +} + +dev().catch((err) => { + console.error("Error starting server:", err); + process.exit(1); +}); diff --git a/data-client/scripts/tunnel.ts b/data-client/scripts/tunnel.ts new file mode 100644 index 0000000..ff4a2a8 --- /dev/null +++ b/data-client/scripts/tunnel.ts @@ -0,0 +1,12 @@ +import { setupDevEnvironment } from "../app/lib/utils/ngrokManager"; + +async function setupTunnel() { + try { + await setupDevEnvironment(); + } catch (error) { + console.error("Tunnel setup failed:", error); + process.exit(1); + } +} + +setupTunnel(); diff --git a/data-client/tsconfig.json b/data-client/tsconfig.json new file mode 100644 index 0000000..9111463 --- /dev/null +++ b/data-client/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "scripts/dev.ts" + ], + "exclude": ["node_modules"] +} diff --git a/data-client/tsconfig.server.json b/data-client/tsconfig.server.json new file mode 100644 index 0000000..e93c603 --- /dev/null +++ b/data-client/tsconfig.server.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "dist", + "noEmit": false, + "jsx": "react", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "types": ["node"] + }, + "include": ["scripts", "app"] +} diff --git a/designer-extension/.env.development b/designer-extension/.env.development new file mode 100644 index 0000000..c0e324a --- /dev/null +++ b/designer-extension/.env.development @@ -0,0 +1 @@ +VITE_DATA_CLIENT_URL=http://localhost:3001 diff --git a/designer-extension/.env.prod b/designer-extension/.env.prod new file mode 100644 index 0000000..a647b9d --- /dev/null +++ b/designer-extension/.env.prod @@ -0,0 +1 @@ +VITE_DATA_CLIENT_URL=https://simpleanalyticsapp.vercel.app diff --git a/designer-extension/.gitignore b/designer-extension/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/designer-extension/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/designer-extension/README.md b/designer-extension/README.md new file mode 100644 index 0000000..74872fd --- /dev/null +++ b/designer-extension/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/designer-extension/bundle.zip b/designer-extension/bundle.zip new file mode 100644 index 0000000000000000000000000000000000000000..7951630ea4ea1e9cc6618376c780904fe62cae90 GIT binary patch literal 66731 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ⅇ!fgY7)39ZB5AN4vL7mq8Q_~w}OniOE%MTZv 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 + + + + + + + Vite + React + TS + + + +
+ + + + \ No newline at end of file diff --git a/designer-extension/package-lock.json b/designer-extension/package-lock.json new file mode 100644 index 0000000..e3184bd --- /dev/null +++ b/designer-extension/package-lock.json @@ -0,0 +1,3376 @@ +{ + "name": "simple-analytics-designer-extension", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "simple-analytics-designer-extension", + "version": "0.0.0", + "dependencies": { + "@tanstack/react-query": "^5.56.2", + "jwt-decode": "^4.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.9.0", + "@types/node": "^22.5.5", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "@webflow/designer-extension-typings": "*", + "chokidar": "^3.6.0", + "eslint": "^9.9.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.1", + "vite": "^5.4.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "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/@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.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", + "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": 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/@typescript-eslint/eslint-plugin": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.55.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.55.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "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/@webflow/designer-extension-typings": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/@webflow/designer-extension-typings/-/designer-extension-typings-2.0.26.tgz", + "integrity": "sha512-j1/cX60IFG6R9Zjl0KmbXydvUMq1qGfwVXBOpxVrrdpl/yxR1TPisDO/BcqFCOnYfm+aKS74QV/v9VTd53D/6Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/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/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "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/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "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/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/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "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": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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-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/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "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/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", + "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.55.0", + "@typescript-eslint/parser": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/designer-extension/package.json b/designer-extension/package.json new file mode 100644 index 0000000..5064f8e --- /dev/null +++ b/designer-extension/package.json @@ -0,0 +1,35 @@ +{ + "name": "simple-analytics-designer-extension", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build:prod": "tsc && vite build --mode prod && webflow extension bundle && mv bundle.zip bundle/prod/bundle_$(date +'%Y-%m-%d_%H-%M-%S').zip", + "build:dev": "tsc && vite build --mode development && webflow extension bundle && mv bundle.zip bundle/development/bundle_$(date +'%Y-%m-%d_%H-%M-%S').zip", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.56.2", + "jwt-decode": "^4.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.9.0", + "@types/node": "^22.5.5", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "@webflow/designer-extension-typings": "*", + "chokidar": "^3.6.0", + "eslint": "^9.9.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.1", + "vite": "^5.4.1" + } +} diff --git a/designer-extension/public/vite.svg b/designer-extension/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/designer-extension/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/designer-extension/src/App.css b/designer-extension/src/App.css new file mode 100644 index 0000000..c249bac --- /dev/null +++ b/designer-extension/src/App.css @@ -0,0 +1,442 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; + font-size: 13px; + color: #ffffff; + background-color: #1e1e1e; + padding: 14px; +} + +.panel { + background: #2d2d2d; + border-radius: 8px; + padding: 14px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +.panel-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid #404040; +} + +.panel-header svg { + width: 24px; + height: 24px; +} + +.panel-header h1 { + font-size: 14px; + font-weight: 600; + color: #ffffff; +} + +.tab-bar { + display: flex; + gap: 0; + margin-bottom: 14px; + border-bottom: 1px solid #404040; +} + +.tab { + flex: 1; + padding: 8px 0; + background: none; + border: none; + border-bottom: 2px solid transparent; + color: #777; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: color 0.2s, border-color 0.2s; +} + +.tab:hover { + color: #bbb; +} + +.tab.active { + color: #fff; + border-bottom-color: #FF4F64; +} + +.info-tab { + padding: 4px 0; +} + +.info-section { + margin-bottom: 16px; +} + +.info-label { + font-weight: 500; + font-size: 13px; + color: #fff; + margin-bottom: 6px; +} + +.info-text { + font-size: 12px; + color: #999; + line-height: 1.5; + margin: 0; +} + +.info-links { + display: flex; + flex-direction: column; + gap: 8px; +} + +.option-group { + margin-bottom: 12px; +} + +.option { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 8px 0; + border-bottom: 1px solid #404040; +} + +.option:last-child { + border-bottom: none; +} + +.option input[type="checkbox"] { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + min-width: 18px; + margin-top: 2px; + cursor: pointer; + background: transparent; + border: 1.5px solid #555; + border-radius: 4px; + transition: background-color 0.2s, border-color 0.2s; + position: relative; +} + +.option input[type="checkbox"]:checked { + background-color: #FF4F64; + border-color: #FF4F64; +} + +.option input[type="checkbox"]:checked::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 11px; + height: 8px; + transform: translate(-50%, -50%); + background: url("data:image/svg+xml,%3Csvg width='11' height='8' viewBox='0 0 11 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.547119 2.91318L3.92212 6.51318L9.54712 0.513184' stroke='white' stroke-width='1.5'/%3E%3C/svg%3E") no-repeat center; +} + +.option input[type="checkbox"]:hover { + border-color: #888; +} + +.option input[type="checkbox"]:checked:hover { + border-color: #FF4F64; +} + +.option-content { + flex: 1; +} + +.option-label { + font-weight: 500; + color: #ffffff; + margin-bottom: 4px; +} + +.option-description { + font-size: 12px; + color: #999999; + line-height: 1.4; +} + +.option-hint { + margin-top: 8px; + padding: 8px 10px; + background: #1e1e1e; + border-radius: 6px; + font-size: 11px; + color: #aaa; + line-height: 1.5; +} + +.option-hint code { + background: #333; + padding: 1px 5px; + border-radius: 3px; + font-size: 11px; + color: #ddd; +} + +.option-hint code.copyable { + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 4px; + transition: background-color 0.2s, color 0.2s; +} + +.option-hint code.copyable:hover { + background: #444; + color: #fff; +} + +.copy-icon { + flex-shrink: 0; + opacity: 0.6; + transition: opacity 0.2s; +} + +.option-hint code.copyable:hover .copy-icon { + opacity: 1; +} + +.advanced-toggle { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 10px 0; + background: none; + border: none; + border-top: 1px solid #404040; + color: #999; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: color 0.2s; +} + +.advanced-toggle:hover { + color: #ccc; +} + +.chevron { + font-size: 10px; + transition: transform 0.2s; + transform: rotate(-90deg); +} + +.chevron-open { + transform: rotate(0deg); +} + +.advanced-section { + margin-bottom: 0; +} + +.option-vertical { + flex-direction: column; + gap: 6px; +} + +.text-input { + width: 100%; + margin-top: 6px; + padding: 8px 10px; + background: #1e1e1e; + border: 1px solid #555; + border-radius: 6px; + color: #fff; + font-size: 12px; + font-family: inherit; + outline: none; + transition: border-color 0.2s; +} + +.text-input:focus { + border-color: #FF4F64; +} + +.text-input::placeholder { + color: #666; +} + +.button-group { + margin-top: 12px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.btn { + width: 100%; + padding: 10px 16px; + border: none; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s, border-color 0.2s, transform 0.1s; + text-align: center; + text-decoration: none; + display: inline-block; +} + +.btn:active { + transform: scale(0.98); +} + +.btn-primary { + background-color: #FF4F64; + color: #fff; +} + +.btn-primary:hover { + background-color: #e8455a; +} + +.btn-primary:disabled { + background-color: #555; + color: #888; + cursor: not-allowed; +} + +.btn-danger { + background-color: transparent; + color: #FF4F64; + border: 1px solid #FF4F64; +} + +.btn-danger:hover { + background-color: rgba(255, 79, 100, 0.1); + border-color: #e8455a; +} + +.btn-danger:disabled { + background-color: transparent; + color: #888; + border-color: #555; + cursor: not-allowed; +} + +.btn-secondary { + background-color: transparent; + color: #ffffff; + border: 1px solid #555; +} + +.btn-secondary:hover { + border-color: #888; +} + +.btn-link { + background: none; + color: #999; + font-size: 12px; + text-decoration: underline; + padding: 8px 0; + border: none; + cursor: pointer; +} + +.btn-link:hover { + color: #ccc; +} + +.status { + margin-top: 12px; + padding: 10px; + border-radius: 6px; + font-size: 12px; + line-height: 1.4; +} + +.status.warning { + background-color: #3a2e1a; + color: #f0b429; +} + +.status.success { + background-color: #1a3a1a; + color: #6fcf6f; +} + +.status.error { + background-color: #3a1a1a; + color: #ff6b6b; +} + +.status.info { + background-color: #1a2a3a; + color: #6bb3ff; +} + +.hidden { + display: none !important; +} + +.section { + margin-bottom: 16px; +} + +.section-text { + font-size: 13px; + color: #999999; + margin-bottom: 12px; + line-height: 1.5; +} + +.panel-footer { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + margin-top: 14px; + padding-top: 12px; + border-top: 1px solid #404040; + font-size: 11px; + color: #777; +} + +.panel-footer a { + color: #999; + text-decoration: underline; +} + +.panel-footer a:hover { + color: #ccc; +} + +.footer-top { + display: flex; + align-items: center; + gap: 4px; +} + +.footer-bottom { + display: flex; + align-items: center; + gap: 8px; +} + +.footer-disconnect { + background: none; + border: none; + color: #777; + font-size: 11px; + cursor: pointer; + text-decoration: underline; + padding: 0; +} + +.footer-disconnect:hover { + color: #ccc; +} diff --git a/designer-extension/src/App.tsx b/designer-extension/src/App.tsx new file mode 100644 index 0000000..f133c0d --- /dev/null +++ b/designer-extension/src/App.tsx @@ -0,0 +1,49 @@ +import { useEffect, useRef } from "react"; +import { useAuth } from "./hooks/useAuth"; +import { AuthScreen } from "./components/AuthScreen"; +import { ConfigPanel } from "./components/ConfigPanel"; +import "./App.css"; + +function App() { + const { sessionToken, isAuthLoading, exchangeAndVerifyIdToken, logout } = useAuth(); + const hasCheckedToken = useRef(false); + + useEffect(() => { + webflow.setExtensionSize("comfortable"); + + if (!hasCheckedToken.current) { + const storedUser = localStorage.getItem("wf_hybrid_user"); + const wasLoggedOut = localStorage.getItem("explicitly_logged_out"); + + if (storedUser && !wasLoggedOut) { + exchangeAndVerifyIdToken(); + } + hasCheckedToken.current = true; + } + + const handleAuthComplete = async (event: MessageEvent) => { + if (event.data === "authComplete") { + localStorage.removeItem("explicitly_logged_out"); + await exchangeAndVerifyIdToken(); + } + }; + + window.addEventListener("message", handleAuthComplete); + return () => { + window.removeEventListener("message", handleAuthComplete); + hasCheckedToken.current = false; + }; + }, [exchangeAndVerifyIdToken]); + + if (isAuthLoading) { + return

Loading...

; + } + + if (sessionToken) { + return ; + } + + return ; +} + +export default App; diff --git a/designer-extension/src/components/AuthScreen.tsx b/designer-extension/src/components/AuthScreen.tsx new file mode 100644 index 0000000..9f1524a --- /dev/null +++ b/designer-extension/src/components/AuthScreen.tsx @@ -0,0 +1,66 @@ +import { useState } from "react"; + +const BASE_URL = import.meta.env.VITE_DATA_CLIENT_URL; + +interface AuthScreenProps { + onAuth: () => void; +} + +export function AuthScreen({ onAuth }: AuthScreenProps) { + const [error, setError] = useState(null); + + async function openAuthScreen() { + setError(null); + + try { + const response = await fetch( + `${BASE_URL}/api/auth/authorize?state=webflow_designer&json=true` + ); + + if (!response.ok) { + throw new Error(`Server returned ${response.status}`); + } + + const { url } = await response.json(); + + const authWindow = window.open(url, "_blank", "width=600,height=600"); + + const checkWindow = setInterval(() => { + if (authWindow?.closed) { + clearInterval(checkWindow); + onAuth(); + } + }, 1000); + } catch (err) { + console.error("Failed to start authorization:", err); + setError("Could not connect to the server. Please check that the data client is running and try again."); + } + } + + return ( +
+
+ + + + +

Simple Analytics

+
+ +
+

+ Connect to Webflow to add Simple Analytics to your site. +

+ {error &&

{error}

} + +
+
+ ); +} diff --git a/designer-extension/src/components/ConfigPanel.tsx b/designer-extension/src/components/ConfigPanel.tsx new file mode 100644 index 0000000..31b8466 --- /dev/null +++ b/designer-extension/src/components/ConfigPanel.tsx @@ -0,0 +1,511 @@ +import { useState, useCallback, useEffect } from "react"; +import { useSites } from "../hooks/useSites"; +import { useInstallScript } from "../hooks/useInstallScript"; +import { StatusMessage } from "./StatusMessage"; +import { ScriptConfig } from "../types/types"; + +const DEFAULT_CONFIG: ScriptConfig = { + trackPageviews: true, + trackEvents: false, + collectDNT: false, + automatedEvents: true, + hashMode: false, + ignorePages: "", + customDomain: "", + overwriteDomain: "", + collectOutbound: true, + collectEmails: true, + collectDownloads: true, + downloadExtensions: "pdf,csv,docx,xlsx,zip,doc,xls", + usePageTitle: true, + collectFullUrls: false, + collectLinkEvents: false, +}; + +function getStorageKey(siteId: string | null): string | null { + return siteId ? `sa_config_${siteId}` : null; +} + +function loadConfig(siteId: string | null): ScriptConfig { + const key = getStorageKey(siteId); + if (!key) return { ...DEFAULT_CONFIG }; + try { + const saved = localStorage.getItem(key); + if (saved) return { ...DEFAULT_CONFIG, ...JSON.parse(saved) }; + } catch { + // corrupt data — fall back to defaults + } + return { ...DEFAULT_CONFIG }; +} + +function saveConfig(siteId: string | null, config: ScriptConfig) { + const key = getStorageKey(siteId); + if (!key) return; + try { + localStorage.setItem(key, JSON.stringify(config)); + } catch { + // storage full or unavailable + } +} + +interface ConfigPanelProps { + sessionToken: string; + onLogout: () => void; +} + +export function ConfigPanel({ sessionToken, onLogout }: ConfigPanelProps) { + const { siteId } = useSites(); + const { + install, + uninstall, + isInstalling, + isRemoving, + isChecking, + isInstalled, + status, + } = useInstallScript(siteId, sessionToken); + const [activeTab, setActiveTab] = useState<"settings" | "info">("settings"); + const [showAdvanced, setShowAdvanced] = useState(false); + const [showEvents, setShowEvents] = useState(false); + + const [config, setConfig] = useState(() => loadConfig(siteId)); + + // Reload config when siteId becomes available + useEffect(() => { + if (siteId) { + setConfig(loadConfig(siteId)); + } + }, [siteId]); + + // Persist config on every change + useEffect(() => { + saveConfig(siteId, config); + }, [siteId, config]); + + function handleToggle(key: keyof ScriptConfig) { + setConfig((prev) => ({ ...prev, [key]: !prev[key] })); + } + + function handleTextChange(key: keyof ScriptConfig, value: string) { + setConfig((prev) => ({ ...prev, [key]: value })); + } + + const [copied, setCopied] = useState(false); + + const handleCopy = useCallback(() => { + const text = "data-sa-link-event"; + try { + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand("copy"); + document.body.removeChild(textarea); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } catch { + // silent fail + } + }, []); + + function handleInstall() { + install(config); + } + + return ( +
+ {/* Tab bar */} +
+ + +
+ + {/* Settings tab */} + {activeTab === "settings" && ( + <> + {/* Main options */} +
+
+ handleToggle("collectLinkEvents")} + /> +
+
Collect link click events
+
+ Track clicks on links with a custom attribute. (default: off) +
+ {config.collectLinkEvents && ( +
+ Add{" "} + + data-sa-link-event + + {copied ? ( + + ) : ( + <> + + + + )} + + {" "} + as a custom attribute on + any link in Webflow (Element Settings → Custom Attributes). + Set the value to your event name, e.g. cta_click. +
+ )} +
+
+ +
+ handleToggle("automatedEvents")} + /> +
+
Collect automated events
+
+ Enable or disable automated events collection. (default: on) +
+
+
+
+ + {/* Advanced Settings */} + + + {showAdvanced && ( +
+
+
+
Custom domain
+
+ Use a custom domain to bypass ad-blockers. (default: empty) +
+ handleTextChange("customDomain", e.target.value)} + /> +
+
+ +
+ handleToggle("collectDNT")} + /> +
+
Collect Do Not Track visits
+
+ Enable analytics for users with Do Not Track enabled. (default: off) +
+
+
+ +
+ handleToggle("trackPageviews")} + /> +
+
Track pageviews
+
+ Automatically track page visits and navigation. (default: on) +
+
+
+ +
+ handleToggle("trackEvents")} + /> +
+
Track events
+
+ Add sa_event() helper for custom event tracking. (default: off) +
+
+
+ +
+
+
Ignored pages
+
+ Disable Simple Analytics on certain pages. (default: empty) +
+ handleTextChange("ignorePages", e.target.value)} + /> +
+
+ +
+
+
Overwrite domain
+
+ Overwrite your domain name here. (default: empty) +
+ handleTextChange("overwriteDomain", e.target.value)} + /> +
+
+ +
+ handleToggle("hashMode")} + /> +
+
Enable hash mode
+
+ Enable hash mode to track URLs with hashes as separate page views. (default: off) +
+
+
+
+ )} + + {/* Events section */} + + + {showEvents && ( +
+
+ handleToggle("collectOutbound")} + /> +
+
Collect outbound links
+
+ It will track clicks on links to other websites. (default: on) +
+
+
+ +
+ handleToggle("collectEmails")} + /> +
+
Collect email clicks
+
+ It will track clicks on email addresses. (default: on) +
+
+
+ +
+ handleToggle("collectDownloads")} + /> +
+
Collect downloads
+
+ It will track downloads of certain files. (default: on) +
+
+
+ +
+
+
Collect downloads extensions
+
+ Extensions to enable download tracking for. +
+ handleTextChange("downloadExtensions", e.target.value)} + /> +
+
+ +
+ handleToggle("usePageTitle")} + /> +
+
Use page title
+
+ Enable or disable title collection. (default: on) +
+
+
+ +
+ handleToggle("collectFullUrls")} + /> +
+
Collect full URLs
+
+ Enable or disable full URL collection. (default: off) +
+
+
+
+ )} + +
+ {isChecking ? ( + + ) : isInstalled ? ( + <> + + + + ) : ( + + )} +
+ + + + )} + + {/* How it works tab */} + {activeTab === "info" && ( +
+
+
What is Simple Analytics?
+

+ Privacy-friendly analytics for your Webflow site. No cookies, fully + GDPR compliant. Get the insights you need without compromising your + visitors' privacy. +

+
+ +
+
How to use this app
+

+ Configure your tracking preferences in the Settings tab, then click + Install Script. The app will automatically add the Simple Analytics + script to your site. After installing, publish your site to start + collecting data. +

+
+ + +
+ )} + +
+ + Powered by{" "} + + Uncode + {" "} + &{" "} + + Klader + + + + + · + + Feedback + + · + v1.0.0 + +
+
+ ); +} diff --git a/designer-extension/src/components/StatusMessage.tsx b/designer-extension/src/components/StatusMessage.tsx new file mode 100644 index 0000000..bb7a783 --- /dev/null +++ b/designer-extension/src/components/StatusMessage.tsx @@ -0,0 +1,13 @@ +import { StatusInfo } from "../types/types"; + +interface StatusMessageProps { + status: StatusInfo; +} + +export function StatusMessage({ status }: StatusMessageProps) { + if (!status.message || !status.type) { + return null; + } + + return
{status.message}
; +} diff --git a/designer-extension/src/hooks/useAuth.ts b/designer-extension/src/hooks/useAuth.ts new file mode 100644 index 0000000..49b7683 --- /dev/null +++ b/designer-extension/src/hooks/useAuth.ts @@ -0,0 +1,213 @@ +import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query"; +import { jwtDecode } from "jwt-decode"; +import { User, DecodedToken } from "../types/types"; + +const base_url = import.meta.env.VITE_DATA_CLIENT_URL; + +interface AuthState { + user: User; + sessionToken: string; +} + +/** + * Custom hook for managing authentication state and token exchange. + * + * Authentication Flow: + * 1. User initiates auth -> exchangeAndVerifyIdToken() + * - Gets ID token from Webflow (Designer APIs) + * - Exchanges it for a session token via API + * + * 2. Token Exchange -> tokenMutation + * - Sends ID token to Data Client + * - Data Client validates and returns session token + * - On success, decodes and stores token + user data + * + * 3. Session Management -> useQuery for token validation + * - Automatically checks for existing valid session + * - Handles token expiration + * - Manages loading states + * + * @returns {Object} Authentication utilities and state + * - user: Current user information + * - sessionToken: Active session token + * - isAuthLoading: Loading state + * - exchangeAndVerifyIdToken: Exchange ID token for session token + * - logout: Clear authentication state + */ +export function useAuth() { + const queryClient = useQueryClient(); + const isExchangingToken = { current: false }; + + // Query for managing auth state and token validation + const { data: authState, isLoading: isAuthLoading } = useQuery({ + queryKey: ["auth"], + queryFn: async () => { + const storedUser = localStorage.getItem("wf_hybrid_user"); + const wasExplicitlyLoggedOut = localStorage.getItem( + "explicitly_logged_out" + ); + + // Return initial state if no stored user or logged out + if (!storedUser || wasExplicitlyLoggedOut) { + return { user: { firstName: "", email: "" }, sessionToken: "" }; + } + + try { + const userData = JSON.parse(storedUser); + if (!userData.sessionToken) { + return { user: { firstName: "", email: "" }, sessionToken: "" }; + } + + // Decode and validate token + const decodedToken = jwtDecode(userData.sessionToken) as DecodedToken; + if (decodedToken.exp * 1000 <= Date.now()) { + // Token expired - clear storage + localStorage.removeItem("wf_hybrid_user"); + return { user: { firstName: "", email: "" }, sessionToken: "" }; + } + + // Return valid auth state + return { + user: { + firstName: decodedToken.user.firstName, + email: decodedToken.user.email, + }, + sessionToken: userData.sessionToken, + }; + } catch (error) { + console.error("Error parsing stored user data:", error); + // Clear invalid data + localStorage.removeItem("wf_hybrid_user"); + return { user: { firstName: "", email: "" }, sessionToken: "" }; + } + }, + staleTime: Infinity, // Never consider the data stale + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + gcTime: 1000 * 60 * 60, // Cache for 1 hour + }); + + // Mutation for exchanging ID token for session token + const tokenMutation = useMutation({ + mutationFn: async (idToken: string) => { + // Get site info from Webflow + const siteInfo = await webflow.getSiteInfo(); + + // Exchange token with backend + const response = await fetch(`${base_url}/api/auth/token`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ idToken: idToken, siteId: siteInfo.siteId }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + `Failed to exchange token: ${JSON.stringify(errorData)}` + ); + } + + const data = await response.json(); + if (!data.sessionToken) { + throw new Error("No session token received"); + } + + return data; + }, + onSuccess: (data) => { + try { + // Decode the new token + const decodedToken = jwtDecode(data.sessionToken) as DecodedToken; + const userData = { + sessionToken: data.sessionToken, + firstName: decodedToken.user.firstName, + email: decodedToken.user.email, + exp: decodedToken.exp, + }; + + // Update localStorage + localStorage.setItem("wf_hybrid_user", JSON.stringify(userData)); + localStorage.removeItem("explicitly_logged_out"); + + // Directly update the query data instead of invalidating + queryClient.setQueryData(["auth"], { + user: { + firstName: decodedToken.user.firstName, + email: decodedToken.user.email, + }, + sessionToken: data.sessionToken, + }); + } catch (error) { + console.error("Error decoding token:", error); + } + }, + }); + + // Function to initiate token exchange process + const exchangeAndVerifyIdToken = async () => { + // Check if we already have a valid session token + const storedUser = localStorage.getItem("wf_hybrid_user"); + if (storedUser) { + try { + const userData = JSON.parse(storedUser); + if (userData.sessionToken) { + const decodedToken = jwtDecode(userData.sessionToken) as DecodedToken; + if (decodedToken.exp * 1000 > Date.now()) { + return; + } + } + } catch (error) { + console.error("Error checking stored token:", error); + } + } + + if (isExchangingToken.current) { + console.log("Token exchange already in progress"); + return; + } + + try { + isExchangingToken.current = true; + // Small delay to prevent rapid retries + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Get new ID token from Webflow + const idToken = await webflow.getIdToken(); + + // Validate token format + if (!idToken || typeof idToken !== "string" || !idToken.trim()) { + throw new Error("Invalid or missing ID token"); + } + + // Exchange token using mutation + await tokenMutation.mutateAsync(idToken); + } catch (error) { + console.error("Detailed error in token exchange:", error); + // Clear storage on error to force re-auth + localStorage.removeItem("wf_hybrid_user"); + } finally { + isExchangingToken.current = false; + } + }; + + // Function to handle user logout + const logout = () => { + // Set logout flag and clear storage + localStorage.setItem("explicitly_logged_out", "true"); + localStorage.removeItem("wf_hybrid_user"); + queryClient.setQueryData(["auth"], { + user: { firstName: "", email: "" }, + sessionToken: "", + }); + queryClient.clear(); + }; + + return { + user: authState?.user || { firstName: "", email: "" }, + sessionToken: authState?.sessionToken || "", + isAuthLoading, + exchangeAndVerifyIdToken, + logout, + }; +} diff --git a/designer-extension/src/hooks/useInstallScript.ts b/designer-extension/src/hooks/useInstallScript.ts new file mode 100644 index 0000000..d4b273f --- /dev/null +++ b/designer-extension/src/hooks/useInstallScript.ts @@ -0,0 +1,140 @@ +import { useState, useEffect } from "react"; +import { ScriptConfig, StatusInfo } from "../types/types"; +import { + registerScript, + applyScript, + checkScriptStatus, + removeScript, +} from "../services/api"; + +/** + * Hook that handles install, update, and uninstall flows for the Simple Analytics script. + */ +export function useInstallScript( + siteId: string | null, + sessionToken: string +) { + const [isInstalling, setIsInstalling] = useState(false); + const [isRemoving, setIsRemoving] = useState(false); + const [isChecking, setIsChecking] = useState(true); + const [isInstalled, setIsInstalled] = useState(false); + const [duplicateDetected, setDuplicateDetected] = useState(false); + const [scriptVersion, setScriptVersion] = useState(null); + const [status, setStatus] = useState({ message: "", type: "" }); + + // Check install status on mount + useEffect(() => { + if (!siteId || !sessionToken) { + setIsChecking(false); + return; + } + + let cancelled = false; + + async function check() { + try { + const result = await checkScriptStatus(siteId!, sessionToken); + if (!cancelled) { + setIsInstalled(result.installed); + setScriptVersion(result.version ?? null); + setDuplicateDetected(result.duplicateDetected); + } + } catch { + // Silent fail — default to not installed + } finally { + if (!cancelled) { + setIsChecking(false); + } + } + } + + check(); + return () => { cancelled = true; }; + }, [siteId, sessionToken]); + + async function install(config: ScriptConfig) { + if (!siteId) { + setStatus({ + message: "Could not detect site. Open this app from the Webflow Designer.", + type: "error", + }); + return; + } + + setIsInstalling(true); + setStatus({ message: "Registering script...", type: "info" }); + + try { + const result = await registerScript(siteId, config, sessionToken); + + setStatus({ message: "Applying to site...", type: "info" }); + + await applyScript(siteId, result.id, result.version, sessionToken); + + setIsInstalled(true); + setScriptVersion(result.version); + + if (result.updated) { + setStatus({ + message: `Script updated to v${result.version}! Publish your site to activate changes.`, + type: "success", + }); + } else { + setStatus({ + message: `Script installed (v${result.version})! Publish your site to activate.`, + type: "success", + }); + } + } catch (err) { + setStatus({ + message: `Error: ${err instanceof Error ? err.message : "Installation failed"}`, + type: "error", + }); + } finally { + setIsInstalling(false); + } + } + + async function uninstall() { + if (!siteId) { + setStatus({ + message: "Could not detect site. Open this app from the Webflow Designer.", + type: "error", + }); + return; + } + + setIsRemoving(true); + setStatus({ message: "Removing script...", type: "info" }); + + try { + await removeScript(siteId, sessionToken); + + setIsInstalled(false); + setScriptVersion(null); + setStatus({ + message: "Script removed! Publish your site to apply changes.", + type: "success", + }); + } catch (err) { + setStatus({ + message: `Error: ${err instanceof Error ? err.message : "Removal failed"}`, + type: "error", + }); + } finally { + setIsRemoving(false); + } + } + + return { + install, + uninstall, + isInstalling, + isRemoving, + isChecking, + isInstalled, + duplicateDetected, + scriptVersion, + status, + }; +} diff --git a/designer-extension/src/hooks/useSites.ts b/designer-extension/src/hooks/useSites.ts new file mode 100644 index 0000000..5b65758 --- /dev/null +++ b/designer-extension/src/hooks/useSites.ts @@ -0,0 +1,20 @@ +import { useQuery } from "@tanstack/react-query"; + +/** + * Get the current site ID from the Webflow Designer API. + * This is all we need - analytics scripts apply site-wide. + */ +export function useSites() { + const { data, isLoading } = useQuery({ + queryKey: ["currentSite"], + queryFn: async () => { + const siteInfo = await webflow.getSiteInfo(); + return siteInfo.siteId; + }, + }); + + return { + siteId: data ?? null, + isLoading, + }; +} diff --git a/designer-extension/src/main.tsx b/designer-extension/src/main.tsx new file mode 100644 index 0000000..f4785a5 --- /dev/null +++ b/designer-extension/src/main.tsx @@ -0,0 +1,30 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; + + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // Prevent duplicate requests in development + retry: false, + // Deduplicate requests made within this time window + staleTime: 1000 * 60 * 5, // 5 minutes + // Don't refetch on window focus + refetchOnWindowFocus: false, + // Deduplicate identical requests made within 2 seconds + gcTime: 2000, + }, + }, +}); + +createRoot(document.getElementById("root")!).render( + + + + + +); + +// Create a client diff --git a/designer-extension/src/services/api.ts b/designer-extension/src/services/api.ts new file mode 100644 index 0000000..cec52ed --- /dev/null +++ b/designer-extension/src/services/api.ts @@ -0,0 +1,96 @@ +import { ScriptConfig, RegisteredScript, ScriptStatus } from "../types/types"; + +const BASE_URL = import.meta.env.VITE_DATA_CLIENT_URL; +const TIMEOUT_MS = 30_000; + +function authHeaders(token: string): Record { + return { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }; +} + +function fetchWithTimeout(url: string, options: RequestInit): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS); + return fetch(url, { ...options, signal: controller.signal }).finally(() => + clearTimeout(timeout) + ); +} + +/** POST /api/scripts/register - register an inline script with Webflow */ +export async function registerScript( + siteId: string, + config: ScriptConfig, + token: string +): Promise { + const response = await fetchWithTimeout(`${BASE_URL}/api/scripts/register`, { + method: "POST", + headers: authHeaders(token), + body: JSON.stringify({ siteId, config }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || "Failed to register script"); + } + + return response.json(); +} + +/** POST /api/scripts/apply - apply a registered script to a site header */ +export async function applyScript( + siteId: string, + scriptId: string, + version: string, + token: string +): Promise { + const response = await fetchWithTimeout(`${BASE_URL}/api/scripts/apply`, { + method: "POST", + headers: authHeaders(token), + body: JSON.stringify({ siteId, scriptId, version }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || "Failed to apply script"); + } +} + +/** GET /api/scripts/status - check if script is installed on site */ +export async function checkScriptStatus( + siteId: string, + token: string +): Promise { + const response = await fetchWithTimeout( + `${BASE_URL}/api/scripts/status?siteId=${encodeURIComponent(siteId)}`, + { + method: "GET", + headers: authHeaders(token), + } + ); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || "Failed to check script status"); + } + + return response.json(); +} + +/** POST /api/scripts/remove - remove script from site */ +export async function removeScript( + siteId: string, + token: string +): Promise { + const response = await fetchWithTimeout(`${BASE_URL}/api/scripts/remove`, { + method: "POST", + headers: authHeaders(token), + body: JSON.stringify({ siteId }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || "Failed to remove script"); + } +} diff --git a/designer-extension/src/types/types.ts b/designer-extension/src/types/types.ts new file mode 100644 index 0000000..96d1582 --- /dev/null +++ b/designer-extension/src/types/types.ts @@ -0,0 +1,65 @@ +// Auth & User Types +export interface TokenResponse { + sessionToken: string; +} + +export interface DecodedToken { + user: User; + exp: number; + iat?: number; + iss?: string; +} + +export interface User { + firstName: string; + email: string; +} + +export interface StoredUser extends User { + sessionToken: string; + exp: number; +} + +// Simple Analytics Script Config +export interface ScriptConfig { + trackPageviews: boolean; + trackEvents: boolean; + collectDNT: boolean; + automatedEvents: boolean; + hashMode: boolean; + ignorePages: string; + customDomain: string; + overwriteDomain: string; + collectOutbound: boolean; + collectEmails: boolean; + collectDownloads: boolean; + downloadExtensions: string; + usePageTitle: boolean; + collectFullUrls: boolean; + collectLinkEvents: boolean; +} + +// Result from the register endpoint +export interface RegisteredScript { + id: string; + version: string; + updated: boolean; +} + +// Result from the status endpoint +export interface ScriptStatus { + installed: boolean; + version?: string; + duplicateDetected: boolean; +} + +// Status message display +export interface StatusInfo { + message: string; + type: "success" | "error" | "info" | ""; +} + +// Environment Variables Type +export interface ImportMetaEnv { + VITE_DATA_CLIENT_URL: string; +} diff --git a/designer-extension/src/vite-env.d.ts b/designer-extension/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/designer-extension/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/designer-extension/tsconfig.app.json b/designer-extension/tsconfig.app.json new file mode 100644 index 0000000..0c8ddd4 --- /dev/null +++ b/designer-extension/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "typeRoots": ["./node_modules/@types", "./node_modules/@webflow"] + }, + "include": ["src"] +} diff --git a/designer-extension/tsconfig.json b/designer-extension/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/designer-extension/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/designer-extension/tsconfig.node.json b/designer-extension/tsconfig.node.json new file mode 100644 index 0000000..0d3d714 --- /dev/null +++ b/designer-extension/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/designer-extension/vite.config.ts b/designer-extension/vite.config.ts new file mode 100644 index 0000000..c498090 --- /dev/null +++ b/designer-extension/vite.config.ts @@ -0,0 +1,104 @@ +import react from '@vitejs/plugin-react'; +import chokidar from 'chokidar'; +import fs from 'fs'; +import path from 'path'; +import { defineConfig, Plugin } from 'vite'; + +const wfDesignerExtensionPlugin = (watchPatterns: string[] = []): Plugin => { + let webflowHTML = ''; + const configPath = path.join('./webflow.json'); + const configContent = fs.readFileSync(configPath, 'utf-8'); + const webflowConfig = JSON.parse(configContent); + + return { + name: 'wf-vite-combined-plugin', + transformIndexHtml: { + order: 'pre', + handler: async (html: string, ctx) => { + if (ctx.server) { + // Development mode + console.log('\x1b[36m%s\x1b[0m', 'Development mode'); + if (!webflowHTML) { + const { name, apiVersion, featureFlags } = webflowConfig; + const template = apiVersion === '2' ? '/template/v2' : '/template'; + const flagQuery = buildFlagQuery(featureFlags); + const url = `https://webflow-ext.com${template}?name=${name}${flagQuery}`; + webflowHTML = await fetch(url).then((res) => res.text()); + } + + // Extract script tags from webflowHTML + const scriptRegex = /]*>([\s\S]*?)<\/script>/gi; + let match; + let scripts = ''; + while ((match = scriptRegex.exec(webflowHTML)) !== null) { + scripts += match[0] + '\n'; + } + + // Insert extracted scripts at the end of the head tag + const finalHTML = html.replace('', `${scripts}`); + return finalHTML; + } + }, + }, + + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + if (req.url === '/__webflow') { + res.writeHead(200, { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': '*', + }); + res.end(configContent); + } else { + next(); + } + }); + + // Watch for changes in specified patterns + const watcher = chokidar.watch(watchPatterns, { + ignoreInitial: true, + persistent: true, + }); + + watcher.on('all', (event, filePath) => { + console.log( + '\x1b[33m%s\x1b[0m', + `File ${filePath} has been ${event}, restarting server...` + ); + + server.restart(); + }); + + // Close the watcher when the server is closed + server?.httpServer?.on('close', () => { + watcher.close(); + }); + }, + }; +}; + +const buildFlagQuery = (featureFlags?: Record): string => + !featureFlags + ? '' + : Object.entries(featureFlags) + .map(([key, value]) => `&ff_${value ? 'on' : 'off'}=${key}`) + .join(''); + +export default defineConfig({ + server: { + port: 1337, + watch: { + usePolling: true, + }, + }, + plugins: [ + react(), + wfDesignerExtensionPlugin(['../data-client/app/api/**/*.ts']), + ], + root: './', + base: './', + build: { + outDir: 'dist', + }, +}); diff --git a/designer-extension/webflow.json b/designer-extension/webflow.json new file mode 100644 index 0000000..dfadd7f --- /dev/null +++ b/designer-extension/webflow.json @@ -0,0 +1,6 @@ +{ + "name": "Simple Analytics", + "publicDir": "dist", + "size": "comfortable", + "apiVersion": "2" +} diff --git a/icon-chevron.svg b/icon-chevron.svg new file mode 100644 index 0000000..8c0e020 --- /dev/null +++ b/icon-chevron.svg @@ -0,0 +1,3 @@ + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0067025 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,309 @@ +{ + "name": "simple-analytics-hybrid", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "simple-analytics-hybrid", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "concurrently": "^9.1.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.0.tgz", + "integrity": "sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7705a8c --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "simple-analytics-hybrid", + "version": "1.0.0", + "description": "Simple Analytics Webflow Hybrid App", + "scripts": { + "dev": "concurrently \"cd data-client && npm run dev\" \"cd designer-extension && npm run dev\"", + "install:all": "cd data-client && npm install && cd ../designer-extension && npm install", + "build": "cd data-client && npm run build && cd ../designer-extension && npm run build:dev" + }, + "license": "MIT", + "dependencies": { + "concurrently": "^9.1.0" + } +} From d555e3eb6b1b18bdb6b839f819151355a690f366 Mon Sep 17 00:00:00 2001 From: Simple Analytics Codex Date: Tue, 6 Oct 2026 11:31:48 +0200 Subject: [PATCH 2/3] Prepare the Webflow app for Simple Analytics hosting Adriaan requested that the commissioned backend run on our infrastructure. Replace Upstash with private Redis, add a production container and the standard deployment workflow, and point the extension at webflow.simpleanalytics.com. Fix site-scoped authentication and release blockers, update vulnerable dependencies, and verify with security tests and container smoke checks. --- .dockerignore | 6 + .github/workflows/ci.yml | 105 + Dockerfile | 18 + data-client/.env.example | 15 +- data-client/.eslintrc.json | 3 - data-client/.gitignore | 3 - data-client/app/api/auth/authorize/route.ts | 54 +- data-client/app/api/auth/callback/route.ts | 146 +- data-client/app/api/auth/token/route.ts | 108 +- data-client/app/api/health/route.ts | 49 +- data-client/app/api/scripts/apply/route.ts | 17 +- data-client/app/api/scripts/register/route.ts | 17 +- data-client/app/api/scripts/remove/route.ts | 17 +- data-client/app/api/scripts/status/route.ts | 19 +- data-client/app/api/sites/route.ts | 40 +- .../app/lib/controllers/scriptController.ts | 11 +- data-client/app/lib/utils/config.ts | 13 + data-client/app/lib/utils/database.ts | 157 +- data-client/app/lib/utils/jwt.ts | 164 +- data-client/app/lib/utils/ngrokManager.ts | 2 + data-client/app/lib/utils/scriptGenerator.ts | 26 +- data-client/eslint.config.mjs | 7 + data-client/next.config.mjs | 51 +- data-client/package-lock.json | 4568 +++++++++++++++++ data-client/package.json | 36 +- data-client/proxy.ts | 22 + data-client/tests/security.test.ts | 93 + data-client/tsconfig.json | 19 +- designer-extension/.env.prod | 2 +- designer-extension/.gitignore | 2 + designer-extension/bundle.zip | Bin 66731 -> 0 bytes designer-extension/package-lock.json | 1141 ++-- designer-extension/package.json | 17 +- designer-extension/scripts/bundle.mjs | 24 + designer-extension/src/App.tsx | 17 +- .../src/components/AuthScreen.tsx | 16 +- designer-extension/src/hooks/useAuth.ts | 237 +- designer-extension/vite.config.ts | 2 +- package-lock.json | 18 +- package.json | 7 +- scripts/smoke.mjs | 37 + 41 files changed, 5807 insertions(+), 1499 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 Dockerfile delete mode 100644 data-client/.eslintrc.json create mode 100644 data-client/app/lib/utils/config.ts create mode 100644 data-client/eslint.config.mjs create mode 100644 data-client/package-lock.json create mode 100644 data-client/proxy.ts create mode 100644 data-client/tests/security.test.ts delete mode 100644 designer-extension/bundle.zip create mode 100644 designer-extension/scripts/bundle.mjs create mode 100644 scripts/smoke.mjs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..33bfbc9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +**/node_modules +**/.next +**/.env* +**/*.tsbuildinfo +.git +designer-extension diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..98721e9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,105 @@ +name: Webflow app + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + cache-dependency-path: | + data-client/package-lock.json + designer-extension/package-lock.json + - run: npm ci --prefix data-client && npm ci --prefix designer-extension + - run: npm run lint --prefix data-client && npm test --prefix data-client + - run: npm run lint --prefix designer-extension && npm run build:prod --prefix designer-extension + - uses: actions/upload-artifact@v4 + with: + name: webflow-extension + path: designer-extension/bundle.zip + - uses: docker/setup-buildx-action@v4 + - uses: docker/build-push-action@v7 + with: + context: . + load: true + tags: webflow-app:smoke + cache-from: type=gha + cache-to: type=gha,mode=max + - name: Container smoke checks + shell: bash + run: | + set -euo pipefail + trap 'docker rm -f webflow-smoke-backend webflow-smoke-redis >/dev/null 2>&1; docker network rm webflow-smoke >/dev/null 2>&1' EXIT + docker network create webflow-smoke + docker run -d --name webflow-smoke-redis --network webflow-smoke redis:7.4.8 redis-server --appendonly yes + for attempt in {1..30}; do + if docker exec webflow-smoke-redis redis-cli ping | grep -q PONG; then break; fi + sleep 1 + done + docker run -d --name webflow-smoke-backend --network webflow-smoke --read-only --tmpfs /tmp -p 127.0.0.1:3039:3000 \ + -e REDIS_URL=redis://webflow-smoke-redis:6379 \ + -e WEBFLOW_CLIENT_ID=smoke-test-client \ + -e WEBFLOW_CLIENT_SECRET=smoke-test-secret \ + -e APP_BASE_URL=http://127.0.0.1:3039 \ + -e DESIGNER_EXTENSION_URI=https://smoke-test.webflow-ext.com webflow-app:smoke + for attempt in {1..30}; do + if curl --fail --silent http://127.0.0.1:3039/api/health >/dev/null; then break; fi + sleep 1 + done + node scripts/smoke.mjs + docker stop webflow-smoke-redis + node scripts/smoke.mjs --unhealthy + + publish: + needs: verify + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/setup-buildx-action@v4 + - uses: docker/build-push-action@v7 + with: + context: . + push: true + tags: ghcr.io/simpleanalytics/webflow-app:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + deploy: + needs: publish + uses: simpleanalytics/github-actions/.github/workflows/deploy.yml@main + secrets: inherit + with: + app: webflow_backend + hosts: esapp04 + strategy: replace + image_repo: ghcr.io/simpleanalytics/webflow-app + image_tag: ${{ github.sha }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c391b0b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM node:24-slim AS build +ENV NEXT_TELEMETRY_DISABLED=1 +WORKDIR /app +COPY data-client/package.json data-client/package-lock.json ./ +RUN npm ci +COPY data-client/ ./ +RUN npm run build + +FROM node:24-slim AS production +ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 HOSTNAME=0.0.0.0 PORT=3000 +WORKDIR /app +COPY --from=build --chown=node:node /app/.next/standalone ./ +COPY --from=build --chown=node:node /app/.next/static ./.next/static +COPY --from=build --chown=node:node /app/public ./public +USER node +EXPOSE 3000 +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 CMD node -e "fetch('http://127.0.0.1:3000/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" +CMD ["node", "server.js"] diff --git a/data-client/.env.example b/data-client/.env.example index 486602c..4006e20 100644 --- a/data-client/.env.example +++ b/data-client/.env.example @@ -1,14 +1,9 @@ WEBFLOW_CLIENT_ID= WEBFLOW_CLIENT_SECRET= +REDIS_URL=redis://127.0.0.1:6379 +APP_BASE_URL=http://localhost:3001 +DESIGNER_EXTENSION_URI=http://localhost:1337 +PORT=3001 -# Upstash Redis (created via Vercel Marketplace integration) -KV_REST_API_URL= -KV_REST_API_TOKEN= - -# Production: Webflow extension origin for CORS -# DESIGNER_EXTENSION_URI=https://698f529c469b4d47089ac992.webflow-ext.com - -# Development only (not needed on Vercel) +# Development tunnel only NGROK_AUTH_TOKEN= -PORT=3001 -DESIGNER_EXTENSION_URI=http://localhost:1337 diff --git a/data-client/.eslintrc.json b/data-client/.eslintrc.json deleted file mode 100644 index 3722418..0000000 --- a/data-client/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["next/core-web-vitals", "next/typescript"] -} diff --git a/data-client/.gitignore b/data-client/.gitignore index 4e0917b..c3c872c 100644 --- a/data-client/.gitignore +++ b/data-client/.gitignore @@ -39,8 +39,5 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -# lock files -package-lock.json - # database .db diff --git a/data-client/app/api/auth/authorize/route.ts b/data-client/app/api/auth/authorize/route.ts index ef67b1a..642ffd7 100644 --- a/data-client/app/api/auth/authorize/route.ts +++ b/data-client/app/api/auth/authorize/route.ts @@ -1,37 +1,29 @@ +import { randomBytes } from "node:crypto"; import { WebflowClient } from "webflow-api"; -import { NextResponse } from "next/server"; -import { OauthScope } from "webflow-api/api/types/OAuthScope"; +import { NextRequest, NextResponse } from "next/server"; +import { getRedis } from "../../../lib/utils/database"; +import { appOrigin, requiredEnv } from "../../../lib/utils/config"; export const dynamic = "force-dynamic"; -/** - * Authorize API Route Handler - * -------------------------- - * Returns the Webflow OAuth URL as JSON when ?json=true is set (for popup flows), - * otherwise redirects directly (for direct navigation). - */ - -const scopes = [ - "sites:read", - "custom_code:read", - "custom_code:write", - "authorized_user:read", -]; - -export async function GET(request: Request) { - const { searchParams } = new URL(request.url); - const isDesigner = searchParams.get("state") === "webflow_designer"; - - const authorizeUrl = WebflowClient.authorizeURL({ - scope: scopes as OauthScope[], - clientId: process.env.WEBFLOW_CLIENT_ID!, - state: isDesigner ? "webflow_designer" : undefined, - }); - - // Return URL as JSON for client-side navigation (avoids bot detection on server redirect) - if (searchParams.get("json") === "true") { - return NextResponse.json({ url: authorizeUrl }); +export async function GET(request: NextRequest) { + try { + const state = randomBytes(32).toString("hex"); + const popup = request.nextUrl.searchParams.get("popup") === "true"; + await (await getRedis()).set(`oauth:${state}`, JSON.stringify({ popup }), { EX: 600 }); + const url = WebflowClient.authorizeURL({ + clientId: requiredEnv("WEBFLOW_CLIENT_ID"), + redirectUri: `${appOrigin()}/api/auth/callback`, + scope: ["sites:read", "custom_code:read", "custom_code:write", "authorized_user:read"], + state, + }); + const response = NextResponse.redirect(url); + response.cookies.set("webflow_oauth_state", state, { + httpOnly: true, secure: appOrigin().startsWith("https:"), sameSite: "lax", + path: "/api/auth", maxAge: 600, + }); + return response; + } catch { + return NextResponse.json({ error: "Authorization is temporarily unavailable" }, { status: 503 }); } - - return NextResponse.redirect(authorizeUrl); } diff --git a/data-client/app/api/auth/callback/route.ts b/data-client/app/api/auth/callback/route.ts index b106b4e..3644379 100644 --- a/data-client/app/api/auth/callback/route.ts +++ b/data-client/app/api/auth/callback/route.ts @@ -1,127 +1,49 @@ +import { timingSafeEqual } from "node:crypto"; import { WebflowClient } from "webflow-api"; import { NextRequest, NextResponse } from "next/server"; -import db from "../../../lib/utils/database"; +import db, { getRedis } from "../../../lib/utils/database"; +import { extensionOrigin, requiredEnv } from "../../../lib/utils/config"; export const dynamic = "force-dynamic"; -/** - * Callback API Route Handler - * ------------------------- - * This route processes the OAuth callback from Webflow after a user authorizes the application. - * - * Flow: - * 1. Receives authorization code from Webflow - * 2. Exchanges code for access token - * 3. Retrieves user's Webflow sites - * 4. Stores site authorization details - * 5. Handles response based on access method (popup vs direct) - * - * @param {NextRequest} request - The incoming request object containing: - * - searchParams: URL parameters including the authorization 'code' - * - headers: Request headers to determine if accessed via popup - * - * @returns {Promise} - * - For popup windows: Returns HTML to close window and notify parent - * - For direct navigation: Redirects to the first authorized Webflow site - * - On error: Returns 400 status if no code provided - * - * @requires {WEBFLOW_CLIENT_ID} - Environment variable for OAuth client ID - * @requires {WEBFLOW_CLIENT_SECRET} - Environment variable for OAuth client secret - */ export async function GET(request: NextRequest) { - // Get the authorization code from the request - const searchParams = request.nextUrl.searchParams; - const code = searchParams.get("code"); - - // If no code, return a 400 error - if (!code) { - return NextResponse.json({ error: "No code provided" }, { status: 400 }); + const state = request.nextUrl.searchParams.get("state"); + const cookie = request.cookies.get("webflow_oauth_state")?.value; + const code = request.nextUrl.searchParams.get("code"); + if (!code || !state || !cookie || !/^[a-f0-9]{64}$/.test(state) || + !/^[a-f0-9]{64}$/.test(cookie) || !timingSafeEqual(Buffer.from(state), Buffer.from(cookie))) { + return NextResponse.json({ error: "Invalid authorization state. Please connect again." }, { status: 400 }); } - try { - // Get Access Token + // Consume the nonce before exchange so callbacks cannot be replayed. + const saved = await (await getRedis()).getDel(`oauth:${state}`); + if (!saved) return NextResponse.json({ error: "Authorization expired. Please connect again." }, { status: 400 }); + const { popup } = JSON.parse(saved); const accessToken = await WebflowClient.getAccessToken({ - clientId: process.env.WEBFLOW_CLIENT_ID!, - clientSecret: process.env.WEBFLOW_CLIENT_SECRET!, - code: code, + clientId: requiredEnv("WEBFLOW_CLIENT_ID"), + clientSecret: requiredEnv("WEBFLOW_CLIENT_SECRET"), code, }); - - // Instantiate the Webflow Client const webflow = new WebflowClient({ accessToken }); - - // Get Site ID to pair with the access token - const sites = await webflow.sites.list(); - const authInfo = await webflow.token.introspect(); - - // Store site authorizations in parallel - const siteList = sites?.sites ?? []; - if (siteList.length > 0) { - await Promise.all( - siteList.map((site) => db.insertSiteAuthorization(site.id, accessToken)) - ); - } - - // Check if the authorization request came from our Webflow designer extension - const isAppPopup = searchParams.get("state") === "webflow_designer"; - console.log("isAppPopup", isAppPopup); - - // If the request is from a popup window, return HTML to close the window - const extensionOrigin = process.env.DESIGNER_EXTENSION_URI || "*"; - if (isAppPopup) { - return new NextResponse( - ` - - - Authorization Complete - - -

Authorization complete! This window will close automatically.

- - - `, - { - headers: { - "Content-Type": "text/html", - }, - } - ); + const { sites = [] } = await webflow.sites.list(); + await Promise.all(sites.map(site => db.insertSiteAuthorization(site.id, accessToken))); + + let response: NextResponse; + if (popup) { + const origin = JSON.stringify(extensionOrigin()).replace(/ + Connected to Webflow

Connected. You can close this window and return to Webflow.

+ `, + { headers: { "Content-Type": "text/html; charset=utf-8" } }); } else { - // If authorized to the Workspace - redirect to the Dashboard - const workspaceIds = - authInfo?.authorization?.authorizedTo?.workspaceIds ?? []; - if (workspaceIds.length > 0) { - return NextResponse.redirect( - `https://webflow.com/dashboard?workspace=${workspaceIds[0]}` - ); - } else { - // If authorized to the Site - redirect to the Designer Extension - const firstSite = siteList[0]; - if (firstSite) { - return NextResponse.redirect( - `https://${firstSite.shortName}.design.webflow.com?app=${process.env.WEBFLOW_CLIENT_ID}` - ); - } - } + const destination = sites[0] + ? `https://${sites[0].shortName}.design.webflow.com?app=${encodeURIComponent(requiredEnv("WEBFLOW_CLIENT_ID"))}` + : "https://webflow.com/dashboard"; + response = NextResponse.redirect(destination); } - } catch (error) { - console.error("Error in callback:", error); - return NextResponse.json( - { error: "Failed to process authorization" }, - { status: 500 } - ); + response.cookies.set("webflow_oauth_state", "", { path: "/api/auth", maxAge: 0 }); + return response; + } catch { + console.error("Webflow authorization failed"); + return NextResponse.json({ error: "Authorization failed. Please connect again." }, { status: 502 }); } } diff --git a/data-client/app/api/auth/token/route.ts b/data-client/app/api/auth/token/route.ts index 4be83bf..d42fc8d 100644 --- a/data-client/app/api/auth/token/route.ts +++ b/data-client/app/api/auth/token/route.ts @@ -4,100 +4,32 @@ import db from "../../../lib/utils/database"; export const dynamic = "force-dynamic"; -/* - Token Exchange API Route - ---------------------- - This route handles user authentication by exchanging a Webflow ID token for a custom session token. - - Flow: - 1. Designer Extension (client) sends a POST request with: - - Site ID - - Webflow ID token - - 2. Server performs these steps: - a) Retrieves an Access Token using the Site ID - - This Access Token proves that the site the request is coming from is authorized to access Webflow's API - - b) Verifies the User's ID Token with Webflow - - Sends the ID token to Webflow's API, making an authenticated request with the retrieved Access Token - - Receives user information if the token is valid - - c) Creates a database record in the UserAuth table - - Stores the User ID and Access Token for future requests - - d) Issues a session token - - Creates a JWT containing user information - - Includes an expiration time for security - - 3. Returns to client: - - Session token - - Expiration timestamp - - Error Handling: - - Returns 401 if Site ID is invalid or unauthorized - - Returns 401 if user verification fails -*/ export async function POST(request: NextRequest) { - // Clone the request since we need to read the body twice - const clonedRequest = request.clone() as NextRequest; - - // Get the Access Token for the Site ID - const accessToken = await jwt.getAccessToken(clonedRequest); - - // If the Access Token is not found, return a 401 Unauthorized response - if (!accessToken) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + let body; + try { body = await request.json(); } catch { + return NextResponse.json({ error: "Invalid request" }, { status: 400 }); + } + const { siteId, idToken } = body ?? {}; + if (typeof siteId !== "string" || !siteId || typeof idToken !== "string" || !idToken) { + return NextResponse.json({ error: "siteId and idToken are required" }, { status: 400 }); } - try { - // Get the ID Token from the request body - const body = await request.json(); - const token = body.idToken; - - // Verify the ID Token with Webflow's API + const accessToken = await db.getAccessTokenFromSiteId(siteId); const response = await fetch("https://api.webflow.com/beta/token/resolve", { method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${accessToken}`, // This Access Token proves the site the request is coming from is authorized to access Webflow's API - }, - body: JSON.stringify({ - idToken: token, - }), + headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}` }, + body: JSON.stringify({ idToken }), + signal: AbortSignal.timeout(10000), }); - - // Validate the response from Webflow - if (!response.ok) { - console.error("Webflow token resolve failed:", response.status); - return NextResponse.json( - { error: "Failed to verify ID token with Webflow" }, - { status: 401 } - ); + if (response.ok) { + const user = await response.json(); + // A valid Designer token for another site must never authorize this site. + if (typeof user.id === "string" && user.id && user.siteId === siteId) { + return NextResponse.json(await jwt.createSessionToken(user.id, siteId)); + } } - - // Get the user information from the response - const user = await response.json(); - console.log("User:", user); - - // Generate a Session Token - const tokenPayload = await jwt.createSessionToken(user); - const sessionToken = tokenPayload.sessionToken; - const expAt = tokenPayload.exp; - - // Store the User ID and Access Token in the database - await db.insertUserAuthorization(user.id, accessToken); - - // Return the Session Token and Expiration Time to the Designer Extension (client) - return NextResponse.json({ sessionToken, exp: expAt }); - } catch (e) { - // If the user is not associated with the site, return a 401 Unauthorized response - console.error("Unauthorized user", e); - return NextResponse.json( - { - error: "Error: User is not associated with authorization for this site", - }, - { status: 401 } - ); + } catch { + // Do not expose upstream tokens, personal data or connection details. } + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } diff --git a/data-client/app/api/health/route.ts b/data-client/app/api/health/route.ts index c47c9ff..ed3c628 100644 --- a/data-client/app/api/health/route.ts +++ b/data-client/app/api/health/route.ts @@ -1,47 +1,18 @@ import { NextResponse } from "next/server"; -import { Redis } from "@upstash/redis"; +import { getRedis } from "../../lib/utils/database"; +import { requiredEnv, appOrigin, extensionOrigin } from "../../lib/utils/config"; export const dynamic = "force-dynamic"; -/** - * GET /api/health - * - * Quick health check that tests Redis connectivity and env var presence. - */ export async function GET() { - const checks: Record = {}; - - // Check env vars (existence only, not values) - checks.WEBFLOW_CLIENT_ID = process.env.WEBFLOW_CLIENT_ID ? "set" : "MISSING"; - checks.WEBFLOW_CLIENT_SECRET = process.env.WEBFLOW_CLIENT_SECRET ? "set" : "MISSING"; - checks.KV_REST_API_URL = process.env.KV_REST_API_URL ? "set" : "MISSING"; - checks.KV_REST_API_TOKEN = process.env.KV_REST_API_TOKEN ? "set" : "MISSING"; - checks.DESIGNER_EXTENSION_URI = process.env.DESIGNER_EXTENSION_URI || "not set (using default)"; - - // Test Redis connection try { - if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) { - const redis = new Redis({ - url: process.env.KV_REST_API_URL, - token: process.env.KV_REST_API_TOKEN, - }); - await redis.ping(); - checks.redis_connection = "OK"; - } else { - checks.redis_connection = "SKIPPED (missing credentials)"; - } - } catch (error) { - checks.redis_connection = `FAILED: ${error instanceof Error ? error.message : String(error)}`; + requiredEnv("WEBFLOW_CLIENT_ID"); + requiredEnv("WEBFLOW_CLIENT_SECRET"); + appOrigin(); + extensionOrigin(); + await (await getRedis()).ping(); + return NextResponse.json({ status: "healthy" }); + } catch { + return NextResponse.json({ status: "unhealthy" }, { status: 503 }); } - - const allGood = checks.WEBFLOW_CLIENT_ID === "set" && - checks.WEBFLOW_CLIENT_SECRET === "set" && - checks.KV_REST_API_URL === "set" && - checks.KV_REST_API_TOKEN === "set" && - checks.redis_connection === "OK"; - - return NextResponse.json({ - status: allGood ? "healthy" : "unhealthy", - checks, - }); } diff --git a/data-client/app/api/scripts/apply/route.ts b/data-client/app/api/scripts/apply/route.ts index c48912e..661679e 100644 --- a/data-client/app/api/scripts/apply/route.ts +++ b/data-client/app/api/scripts/apply/route.ts @@ -12,13 +12,6 @@ export const dynamic = "force-dynamic"; * Applies the registered script to the site header via upsertCustomCode. */ export async function POST(request: NextRequest) { - const clonedRequest = request.clone() as NextRequest; - const accessToken = await jwt.verifyAuth(clonedRequest); - - if (!accessToken) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - try { const body = await request.json(); const { siteId, scriptId, version } = body as { @@ -34,14 +27,18 @@ export async function POST(request: NextRequest) { ); } + const accessToken = await jwt.verifyAuth(request, siteId); + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const webflow = new WebflowClient({ accessToken }); await applySiteScript(webflow, siteId, scriptId, version); return NextResponse.json({ success: true }); - } catch (error) { - console.error("Error in /api/scripts/apply:", error); + } catch { return NextResponse.json( - { error: error instanceof Error ? error.message : "Failed to apply script" }, + { error: "Failed to apply script" }, { status: 500 } ); } diff --git a/data-client/app/api/scripts/register/route.ts b/data-client/app/api/scripts/register/route.ts index b7d20a8..4be6595 100644 --- a/data-client/app/api/scripts/register/route.ts +++ b/data-client/app/api/scripts/register/route.ts @@ -13,13 +13,6 @@ export const dynamic = "force-dynamic"; * Generates the inline script and registers it with Webflow. */ export async function POST(request: NextRequest) { - const clonedRequest = request.clone() as NextRequest; - const accessToken = await jwt.verifyAuth(clonedRequest); - - if (!accessToken) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - try { const body = await request.json(); const { siteId, config } = body as { siteId: string; config: ScriptConfig }; @@ -28,15 +21,19 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "siteId is required" }, { status: 400 }); } + const accessToken = await jwt.verifyAuth(request, siteId); + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const sourceCode = generateScript(config); const webflow = new WebflowClient({ accessToken }); const result = await registerInlineScript(webflow, siteId, sourceCode); return NextResponse.json(result); - } catch (error) { - console.error("Error in /api/scripts/register:", error); + } catch { return NextResponse.json( - { error: error instanceof Error ? error.message : "Failed to register script" }, + { error: "Failed to register script" }, { status: 500 } ); } diff --git a/data-client/app/api/scripts/remove/route.ts b/data-client/app/api/scripts/remove/route.ts index 57e3733..5c716cb 100644 --- a/data-client/app/api/scripts/remove/route.ts +++ b/data-client/app/api/scripts/remove/route.ts @@ -12,13 +12,6 @@ export const dynamic = "force-dynamic"; * Removes all scripts applied by our app from the site. */ export async function POST(request: NextRequest) { - const clonedRequest = request.clone() as NextRequest; - const accessToken = await jwt.verifyAuth(clonedRequest); - - if (!accessToken) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - try { const body = await request.json(); const { siteId } = body as { siteId: string }; @@ -27,14 +20,18 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "siteId is required" }, { status: 400 }); } + const accessToken = await jwt.verifyAuth(request, siteId); + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const webflow = new WebflowClient({ accessToken }); await removeSiteScript(webflow, siteId); return NextResponse.json({ success: true }); - } catch (error) { - console.error("Error in /api/scripts/remove:", error); + } catch { return NextResponse.json( - { error: error instanceof Error ? error.message : "Failed to remove script" }, + { error: "Failed to remove script" }, { status: 500 } ); } diff --git a/data-client/app/api/scripts/status/route.ts b/data-client/app/api/scripts/status/route.ts index 30d66e4..c005f73 100644 --- a/data-client/app/api/scripts/status/route.ts +++ b/data-client/app/api/scripts/status/route.ts @@ -8,17 +8,10 @@ export const dynamic = "force-dynamic"; /** * GET /api/scripts/status?siteId=... * - * Checks if a Simple Analytics script is already registered for the site. + * Checks if a Simple Analytics script is currently applied to the site. * Returns { installed: boolean, version?: string } */ export async function GET(request: NextRequest) { - const clonedRequest = request.clone() as NextRequest; - const accessToken = await jwt.verifyAuth(clonedRequest); - - if (!accessToken) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - try { const { searchParams } = new URL(request.url); const siteId = searchParams.get("siteId"); @@ -27,14 +20,18 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: "siteId is required" }, { status: 400 }); } + const accessToken = await jwt.verifyAuth(request, siteId); + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const webflow = new WebflowClient({ accessToken }); const status = await checkScriptStatus(webflow, siteId); return NextResponse.json(status); - } catch (error) { - console.error("Error in /api/scripts/status:", error); + } catch { return NextResponse.json( - { error: error instanceof Error ? error.message : "Failed to check script status" }, + { error: "Failed to check script status" }, { status: 500 } ); } diff --git a/data-client/app/api/sites/route.ts b/data-client/app/api/sites/route.ts index f35d766..8237756 100644 --- a/data-client/app/api/sites/route.ts +++ b/data-client/app/api/sites/route.ts @@ -4,38 +4,16 @@ import jwt from "../../lib/utils/jwt"; export const dynamic = "force-dynamic"; -/* - Sites API Route - --------------- - This route handles the GET request from the client to retrieve the list of sites associated with the user. -*/ export async function GET(request: NextRequest) { try { - // Clone the request since we need to read the body twice - const clonedRequest = request.clone() as NextRequest; - - // Verify the user is authenticated - const accessToken = await jwt.verifyAuth(clonedRequest); - - // If the user is not authenticated, return a 401 Unauthorized response - if (!accessToken) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - // Create a new WebflowClient with the Access Token - const webflow = new WebflowClient({ accessToken }); - - // Get the list of sites associated with the user - const data = await webflow.sites.list(); - - // Return the list of sites to the client - return NextResponse.json({ data }); - } catch (error) { - // If an error occurs, return a 500 Internal Server Error response - console.error("Error handling authenticated request:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + const token = /^Bearer ([^ ]+)$/i.exec(request.headers.get("authorization") ?? "")?.[1]; + if (!token) throw new Error("Unauthorized"); + const { siteId } = await jwt.verifySession(token); + const accessToken = await jwt.verifyAuth(request, siteId); + if (!accessToken) throw new Error("Unauthorized"); + const site = await new WebflowClient({ accessToken }).sites.get(siteId); + return NextResponse.json({ data: { sites: [site] } }); + } catch { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } diff --git a/data-client/app/lib/controllers/scriptController.ts b/data-client/app/lib/controllers/scriptController.ts index bd38ede..e51b555 100644 --- a/data-client/app/lib/controllers/scriptController.ts +++ b/data-client/app/lib/controllers/scriptController.ts @@ -103,14 +103,16 @@ export async function checkScriptStatus( }) ); - const installed = matching.length > 0; - const version = installed ? (matching[0].version ?? "1.0.0") : undefined; + const customCode = await webflow.sites.scripts.getCustomCode(siteId); + const applied = customCode?.scripts || []; + const installedScript = applied.find(script => matching.some(registered => registered.id === script.id)); + const installed = Boolean(installedScript); + const version = installedScript?.version; // Check for duplicate SA scripts on the site (e.g. manually added) let duplicateDetected = false; try { - const customCode = await webflow.sites.scripts.getCustomCode(siteId); - const scripts = customCode?.scripts || []; + const scripts = applied; // Count how many applied scripts reference simpleanalyticscdn in their source // Our app registers scripts with displayName "Simple Analytics", @@ -124,7 +126,6 @@ export async function checkScriptStatus( const registered = (existingScripts.registeredScripts || []).find( (r) => r.id === s.id ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any const reg = registered as Record | undefined; if (typeof reg?.sourceUrl === "string" && reg.sourceUrl.includes("simpleanalyticscdn.com")) return true; if (typeof reg?.source === "string" && reg.source.includes("simpleanalyticscdn.com")) return true; diff --git a/data-client/app/lib/utils/config.ts b/data-client/app/lib/utils/config.ts new file mode 100644 index 0000000..54b0e2b --- /dev/null +++ b/data-client/app/lib/utils/config.ts @@ -0,0 +1,13 @@ +export function requiredEnv(name: string): string { + const value = process.env[name]; + if (!value) throw new Error(`${name} is required`); + return value; +} + +export function appOrigin(): string { + return new URL(requiredEnv("APP_BASE_URL")).origin; +} + +export function extensionOrigin(): string { + return new URL(requiredEnv("DESIGNER_EXTENSION_URI")).origin; +} diff --git a/data-client/app/lib/utils/database.ts b/data-client/app/lib/utils/database.ts index 7f580c8..cb6cf40 100644 --- a/data-client/app/lib/utils/database.ts +++ b/data-client/app/lib/utils/database.ts @@ -1,135 +1,54 @@ -import { Redis } from "@upstash/redis"; -import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from "crypto"; +import { createClient } from "redis"; +import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from "node:crypto"; +import { requiredEnv } from "./config"; -/** - * Database Utility (Upstash Redis) - * -------------------------------- - * This module provides functions to interact with Upstash Redis for token storage. - * Tokens are encrypted at rest using AES-256-GCM derived from WEBFLOW_CLIENT_SECRET. - * - * Key structure: - * site: → encrypted access token - * user: → encrypted access token - */ - -const ENCRYPTION_ALGORITHM = "aes-256-gcm"; - -function deriveKey(): Buffer { - const secret = process.env.WEBFLOW_CLIENT_SECRET; - if (!secret) { - throw new Error("WEBFLOW_CLIENT_SECRET is required for token encryption"); - } - return scryptSync(secret, "simple-analytics-salt", 32); -} - -function encrypt(plaintext: string): string { - const key = deriveKey(); +// Only ciphertext is persisted, including in Redis AOF/snapshots. +export function encrypt(plaintext: string): string { + const key = scryptSync(requiredEnv("WEBFLOW_CLIENT_SECRET"), "simple-analytics-salt", 32); const iv = randomBytes(12); - const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv); - const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]); - const authTag = cipher.getAuthTag(); - return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`; + const cipher = createCipheriv("aes-256-gcm", key, iv); + const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]); + return `${iv.toString("hex")}:${cipher.getAuthTag().toString("hex")}:${ciphertext.toString("hex")}`; } -function decrypt(encoded: string): string { - const key = deriveKey(); - const [ivHex, authTagHex, ciphertextHex] = encoded.split(":"); - const iv = Buffer.from(ivHex, "hex"); - const authTag = Buffer.from(authTagHex, "hex"); - const ciphertext = Buffer.from(ciphertextHex, "hex"); - const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv); - decipher.setAuthTag(authTag); - return decipher.update(ciphertext) + decipher.final("utf8"); +export function decrypt(encoded: string): string { + const key = scryptSync(requiredEnv("WEBFLOW_CLIENT_SECRET"), "simple-analytics-salt", 32); + const [iv, tag, ciphertext] = encoded.split(":"); + const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "hex")); + decipher.setAuthTag(Buffer.from(tag, "hex")); + return Buffer.concat([decipher.update(Buffer.from(ciphertext, "hex")), decipher.final()]).toString("utf8"); } -/** Singleton Redis client */ -let redis: Redis | null = null; +let client: ReturnType | undefined; +let connecting: Promise | undefined; -function getRedis(): Redis { - if (!redis) { - redis = new Redis({ - url: process.env.KV_REST_API_URL!, - token: process.env.KV_REST_API_TOKEN!, +export async function getRedis() { + if (!client) { + client = createClient({ + url: requiredEnv("REDIS_URL"), + disableOfflineQueue: true, + socket: { connectTimeout: 2000, reconnectStrategy: false }, }); + // Never log connection URLs, credentials or token values. + client.on("error", () => console.error("Webflow Redis connection failed")); } - return redis; -} - -/** - * Stores a site authorization (siteId → accessToken). - */ -export async function insertSiteAuthorization( - siteId: string, - accessToken: string -) { - const db = getRedis(); - const encryptedToken = encrypt(accessToken); - await db.set(`site:${siteId}`, encryptedToken); - console.log("Site authorization pairing updated."); -} - -/** - * Stores a user authorization (userId → accessToken). - */ -export async function insertUserAuthorization( - userId: string, - accessToken: string -) { - const db = getRedis(); - const encryptedToken = encrypt(accessToken); - await db.set(`user:${userId}`, encryptedToken); - console.log("User access token pairing updated."); -} - -/** - * Retrieves the access token for a given site ID. - */ -export async function getAccessTokenFromSiteId( - siteId: string -): Promise { - const db = getRedis(); - const encryptedToken = await db.get(`site:${siteId}`); - - if (!encryptedToken) { - throw new Error("No access token found or site does not exist"); + if (!client.isReady) { + connecting ??= client.connect().finally(() => { connecting = undefined; }); + await connecting; } - - return decrypt(encryptedToken); + return client.withCommandOptions({ abortSignal: AbortSignal.timeout(2000) }); } -/** - * Retrieves the access token for a given user ID. - */ -export async function getAccessTokenFromUserId( - userId: string -): Promise { - const db = getRedis(); - const encryptedToken = await db.get(`user:${userId}`); - - if (!encryptedToken) { - throw new Error("No access token found or user does not exist"); - } - - return decrypt(encryptedToken); +export async function insertSiteAuthorization(siteId: string, accessToken: string) { + const db = await getRedis(); + await db.set(`site:${siteId}`, encrypt(accessToken)); } -/** - * Clears all data from the database. - * Note: In production, this uses FLUSHDB which removes ALL keys. - * Use with caution. - */ -export async function clearDatabase() { - const db = getRedis(); - await db.flushdb(); - console.log("Database cleared successfully"); +export async function getAccessTokenFromSiteId(siteId: string): Promise { + const db = await getRedis(); + const ciphertext = await db.get(`site:${siteId}`); + if (!ciphertext) throw new Error("Site is not authorized"); + return decrypt(ciphertext); } -const database = { - getAccessTokenFromSiteId, - getAccessTokenFromUserId, - insertSiteAuthorization, - insertUserAuthorization, - clearDatabase, -}; - -export default database; +export default { insertSiteAuthorization, getAccessTokenFromSiteId }; diff --git a/data-client/app/lib/utils/jwt.ts b/data-client/app/lib/utils/jwt.ts index 81d659c..e235170 100644 --- a/data-client/app/lib/utils/jwt.ts +++ b/data-client/app/lib/utils/jwt.ts @@ -1,145 +1,45 @@ import { SignJWT, jwtVerify } from "jose"; -import { NextRequest } from "next/server"; +import type { NextRequest } from "next/server"; import db from "./database"; +import { requiredEnv } from "./config"; -/* - JWT Utility Functions - --------------------- - This file provides a comprehensive set of utilities for handling JSON Web Tokens (JWT) - in a Next.js application. It manages authentication between Webflow's Designer Extension - and our backend API. +const issuer = "simpleanalytics-webflow"; +const audience = "webflow-designer"; +const secret = () => new TextEncoder().encode(requiredEnv("WEBFLOW_CLIENT_SECRET")); - Architecture: - - Uses JOSE library for JWT operations - - Implements HS256 algorithm for token signing - - Stores access tokens in database - - Handles token verification and extraction - - Security: - - Tokens expire after 24 hours - - Uses WEBFLOW_CLIENT_SECRET for signing - - Validates signatures on every request - - Safely handles verification failures -*/ - -interface User { - id: string; - email: string; -} - -interface JWTPayload { - user: User; -} - -/** - * Creates a signed JWT session token for authenticated users - * - * @param user - Object containing user ID and email - * @returns Promise containing: - * - sessionToken: Signed JWT token - * - exp: Token expiration timestamp - * - * Flow: - * 1. Encodes signing secret - * 2. Creates and signs token with 24h expiration - * 3. Verifies token and extracts expiration - */ -const createSessionToken = async (user: User) => { - // Encode secret - const secret = new TextEncoder().encode(process.env.WEBFLOW_CLIENT_SECRET); - - // Create session token - const sessionToken = await new SignJWT({ user }) +export async function createSessionToken(userId: string, siteId: string) { + const exp = Math.floor(Date.now() / 1000) + 3600; + const sessionToken = await new SignJWT({ siteId }) .setProtectedHeader({ alg: "HS256" }) - .setExpirationTime("24h") - .sign(secret); - - // Decode token - const decodedToken = await jwtVerify(sessionToken, secret); - - return { - sessionToken, - exp: decodedToken.payload.exp, - }; -}; - -/** - * Validates JWT token from request and retrieves associated access token - * - * @param request - Incoming Next.js request - * @returns Promise - Access token if valid, null otherwise - * - * Flow: - * 1. Extracts Bearer token from Authorization header - * 2. Verifies token signature and expiration - * 3. Retrieves associated access token from database - * 4. Returns null if any step fails - */ -const verifyAuth = async (request: NextRequest) => { - const authHeader = request.headers.get("authorization"); // Get authorization header from request - const sessionToken = authHeader?.split(" ")[1]; // Extract the token from 'Bearer ' + .setSubject(userId) + .setIssuer(issuer) + .setAudience(audience) + .setIssuedAt() + .setExpirationTime(exp) + .sign(secret()); + return { sessionToken, exp }; +} - // If no session token, return null - if (!sessionToken) { - return null; +export async function verifySession(sessionToken: string) { + const { payload } = await jwtVerify(sessionToken, secret(), { + algorithms: ["HS256"], issuer, audience, requiredClaims: ["exp", "sub", "siteId"], + }); + if (typeof payload.siteId !== "string" || !payload.siteId || !payload.sub) { + throw new Error("Invalid session"); } + return { siteId: payload.siteId, userId: payload.sub }; +} +export async function verifyAuth(request: NextRequest, siteId: unknown) { + const match = /^Bearer ([^ ]+)$/i.exec(request.headers.get("authorization") ?? ""); + if (!match || typeof siteId !== "string" || !siteId) return null; try { - // Verify session token - const secret = new TextEncoder().encode(process.env.WEBFLOW_CLIENT_SECRET); - const { payload } = (await jwtVerify(sessionToken, secret)) as { - payload: JWTPayload; - }; - - // Get user ID from payload - const userId = payload.user.id; - - // Get access token from user ID - const accessToken = await db.getAccessTokenFromUserId(userId as string); - - // Return access token - return accessToken; + const session = await verifySession(match[1]); + if (session.siteId !== siteId) return null; + return await db.getAccessTokenFromSiteId(siteId); } catch { - // If error, return null - return null; - } -}; - -/** - * Retrieves Webflow access token for a specific site - * - * @param request - Incoming request containing siteId in body - * @returns Promise - Access token if found, null otherwise - * - * Flow: - * 1. Extracts siteId from request body - * 2. Looks up access token in database - * 3. Handles errors and missing tokens - */ -const getAccessToken = async (request: NextRequest) => { - try { - const body = await request.json(); - const { siteId } = body; - - // Get the access token from your database or wherever you store it - const accessToken = await db.getAccessTokenFromSiteId(siteId); - - if (!accessToken) { - console.error(`No access token found for site ${siteId}`); - return null; - } - - return accessToken; - } catch (error) { - console.error("Error getting access token:", error); return null; } -}; - -const jwtUtils = { - createSessionToken, - verifyAuth, - getAccessToken, -}; +} -export default jwtUtils; +export default { createSessionToken, verifyAuth, verifySession }; diff --git a/data-client/app/lib/utils/ngrokManager.ts b/data-client/app/lib/utils/ngrokManager.ts index ea59015..a70aaec 100644 --- a/data-client/app/lib/utils/ngrokManager.ts +++ b/data-client/app/lib/utils/ngrokManager.ts @@ -42,6 +42,8 @@ function drawBox(content: string): string { * @returns {string} The string with ANSI color codes removed. */ function stripAnsi(string: string): string { + // ANSI escape is intentionally a control character. + // eslint-disable-next-line no-control-regex return string.replace(/\u001b\[[0-9]{1,2}m/g, ""); } diff --git a/data-client/app/lib/utils/scriptGenerator.ts b/data-client/app/lib/utils/scriptGenerator.ts index 0b52368..551fd55 100644 --- a/data-client/app/lib/utils/scriptGenerator.ts +++ b/data-client/app/lib/utils/scriptGenerator.ts @@ -21,7 +21,11 @@ export interface ScriptConfig { */ function getBaseUrl(config: ScriptConfig): string { if (config.customDomain) { - return `https://${config.customDomain}`; + const url = new URL(`https://${config.customDomain}`); + if (url.hostname !== config.customDomain || url.port || url.username || url.password || url.pathname !== "/" || url.search || url.hash) { + throw new Error("Custom domain must be a hostname"); + } + return url.origin; } return "https://scripts.simpleanalyticscdn.com"; } @@ -32,6 +36,14 @@ function getBaseUrl(config: ScriptConfig): string { * Ported from the original Simple Analytics Webflow app (public/index.js). */ export function generateScript(config: ScriptConfig): string { + if (!config || typeof config !== "object") throw new Error("Invalid script configuration"); + for (const key of ["ignorePages", "customDomain", "overwriteDomain", "downloadExtensions"] as const) { + if (typeof config[key] !== "string" || config[key].length > 2048) throw new Error("Invalid script configuration"); + } + for (const key of ["trackPageviews", "trackEvents", "collectDNT", "automatedEvents", "hashMode", "collectOutbound", "collectEmails", "collectDownloads", "usePageTitle", "collectFullUrls", "collectLinkEvents"] as const) { + if (typeof config[key] !== "boolean") throw new Error("Invalid script configuration"); + } + const quote = (value: string) => JSON.stringify(value).replace(/.*)", - }, - ], - }, - ]; - }, + output: "standalone", + outputFileTracingRoot: import.meta.dirname, + turbopack: { root: import.meta.dirname }, + poweredByHeader: false, + serverExternalPackages: ["redis"], }; export default nextConfig; diff --git a/data-client/package-lock.json b/data-client/package-lock.json new file mode 100644 index 0000000..0cb4a70 --- /dev/null +++ b/data-client/package-lock.json @@ -0,0 +1,4568 @@ +{ + "name": "simple-analytics-data-client", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "simple-analytics-data-client", + "version": "0.1.0", + "dependencies": { + "jose": "^6.2.0", + "next": "16.3.8", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "redis": "^5.12.1", + "webflow-api": "3.3.4" + }, + "devDependencies": { + "@eslint/js": "^9.39.0", + "@ngrok/ngrok": "^1.4.1", + "@types/node": "^24.0.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "chalk": "^5.3.0", + "eslint": "^9.39.0", + "tsx": "^4.19.2", + "typescript": "^5.3.3", + "typescript-eslint": "^8.0.1" + }, + "engines": { + "node": ">=24" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@next/env": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.8.tgz", + "integrity": "sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.8.tgz", + "integrity": "sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.8.tgz", + "integrity": "sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.8.tgz", + "integrity": "sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.8.tgz", + "integrity": "sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.8.tgz", + "integrity": "sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.8.tgz", + "integrity": "sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.8.tgz", + "integrity": "sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.8.tgz", + "integrity": "sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok/-/ngrok-1.7.0.tgz", + "integrity": "sha512-P06o9TpxrJbiRbHQkiwy/rUrlXRupc+Z8KT4MiJfmcdWxvIdzjCaJOdnNkcOTs6DMyzIOefG5tvk/HLdtjqr0g==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@ngrok/ngrok-android-arm64": "1.7.0", + "@ngrok/ngrok-darwin-arm64": "1.7.0", + "@ngrok/ngrok-darwin-universal": "1.7.0", + "@ngrok/ngrok-darwin-x64": "1.7.0", + "@ngrok/ngrok-freebsd-x64": "1.7.0", + "@ngrok/ngrok-linux-arm-gnueabihf": "1.7.0", + "@ngrok/ngrok-linux-arm64-gnu": "1.7.0", + "@ngrok/ngrok-linux-arm64-musl": "1.7.0", + "@ngrok/ngrok-linux-x64-gnu": "1.7.0", + "@ngrok/ngrok-linux-x64-musl": "1.7.0", + "@ngrok/ngrok-win32-arm64-msvc": "1.7.0", + "@ngrok/ngrok-win32-ia32-msvc": "1.7.0", + "@ngrok/ngrok-win32-x64-msvc": "1.7.0" + } + }, + "node_modules/@ngrok/ngrok-android-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-android-arm64/-/ngrok-android-arm64-1.7.0.tgz", + "integrity": "sha512-8tco3ID6noSaNy+CMS7ewqPoIkIM6XO5COCzsUp3Wv3XEbMSyn65RN6cflX2JdqLfUCHcMyD0ahr9IEiHwqmbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-arm64/-/ngrok-darwin-arm64-1.7.0.tgz", + "integrity": "sha512-+dmJSOzSO+MNDVrPOca2yYDP1W3KfP4qOlAkarIeFRIfqonQwq3QCBmcR7HAlZocLsSqEwyG6KP4RRvAuT0WGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-universal": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-universal/-/ngrok-darwin-universal-1.7.0.tgz", + "integrity": "sha512-fDEfewyE2pWGFBhOSwQZObeHUkc65U1l+3HIgSOe094TMHsqmyJD0KTCgW9KSn0VP4OvDZbAISi1T3nvqgZYhQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-x64/-/ngrok-darwin-x64-1.7.0.tgz", + "integrity": "sha512-+fwMi5uHd9G8BS42MMa9ye6exI5lwTcjUO6Ut497Vu0qgLONdVRenRqnEePV+Q3KtQR7NjqkMnomVfkr9MBjtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-freebsd-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-freebsd-x64/-/ngrok-freebsd-x64-1.7.0.tgz", + "integrity": "sha512-2OGgbrjy3yLRrqAz5N6hlUKIWIXSpR5RjQa2chtZMsSbszQ6c9dI+uVQfOKAeo05tHMUgrYAZ7FocC+ig0dzdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm-gnueabihf": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm-gnueabihf/-/ngrok-linux-arm-gnueabihf-1.7.0.tgz", + "integrity": "sha512-SN9YIfEQiR9xN90QVNvdgvAemqMLoFVSeTWZs779145hQMhvF9Qd9rnWi6J+2uNNK10OczdV1oc/nq1es7u/3g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-gnu/-/ngrok-linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-KDMgzPKFU2kbpVSaA2RZBBia5IPdJEe063YlyVFnSMJmPYWCUnMwdybBsucXfV9u1Lw/ZjKTKotIlbTWGn3HGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-musl/-/ngrok-linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-e66vUdVrBlQ0lT9ZdamB4U604zt5Gualt8/WVcUGzbu8s5LajWd6g/mzZCUjK4UepjvMpfgmCp1/+rX7Rk8d5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-gnu/-/ngrok-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-M6gF0DyOEFqXLfWxObfL3bxYZ4+PnKBHuyLVaqNfFN9Y5utY2mdPOn5422Ppbk4XoIK5/YkuhRqPJl/9FivKEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-musl/-/ngrok-linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-4Ijm0dKeoyzZTMaYxR2EiNjtlK81ebflg/WYIO1XtleFrVy4UJEGnxtxEidYoT4BfCqi4uvXiK2Mx216xXKvog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-arm64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-arm64-msvc/-/ngrok-win32-arm64-msvc-1.7.0.tgz", + "integrity": "sha512-u7qyWIJI2/YG1HTBnHwUR1+Z2tyGfAsUAItJK/+N1G0FeWJhIWQvSIFJHlaPy4oW1Dc8mSDBX9qvVsiQgLaRFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-ia32-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-ia32-msvc/-/ngrok-win32-ia32-msvc-1.7.0.tgz", + "integrity": "sha512-/UdYUsLNv/Q8j9YJsyIfq/jLCoD8WP+NidouucTUzSoDtmOsXBBT3itLrmPiZTEdEgKiFYLuC1Zon8XQQvbVLA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-x64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-x64-msvc/-/ngrok-win32-x64-msvc-1.7.0.tgz", + "integrity": "sha512-UFJg/duEWzZlLkEs61Gz6/5nYhGaKI62I8dvUGdBR3NCtIMagehnFaFxmnXZldyHmCM8U0aCIFNpWRaKcrQkoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@redis/bloom": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.12.1.tgz", + "integrity": "sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/client": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.12.1.tgz", + "integrity": "sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/json": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.12.1.tgz", + "integrity": "sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/search": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.12.1.tgz", + "integrity": "sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/time-series": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.12.1.tgz", + "integrity": "sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz", + "integrity": "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.1.tgz", + "integrity": "sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/type-utils": "8.71.1", + "@typescript-eslint/utils": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.71.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.12.tgz", + "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.1.tgz", + "integrity": "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.1.tgz", + "integrity": "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.71.1", + "@typescript-eslint/types": "^8.71.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.1.tgz", + "integrity": "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.1.tgz", + "integrity": "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.1.tgz", + "integrity": "sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1", + "@typescript-eslint/utils": "8.71.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.1.tgz", + "integrity": "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.1.tgz", + "integrity": "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.71.1", + "@typescript-eslint/tsconfig-utils": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.1.tgz", + "integrity": "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.1.tgz", + "integrity": "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", + "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", + "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.6.tgz", + "integrity": "sha512-sd+Q65fjlWCYWtZKXiKfrUc8d+4jtp/8f0W2NkwzLtoW4bI6UDnWusLWIurHnmurW0XShIRxpwiOX4EoPtXUAg==", + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.3", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001814", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz", + "integrity": "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-node": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz", + "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "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/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "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/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.8.tgz", + "integrity": "sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==", + "license": "MIT", + "dependencies": { + "@next/env": "16.3.8", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.3.8", + "@next/swc-darwin-x64": "16.3.8", + "@next/swc-linux-arm64-gnu": "16.3.8", + "@next/swc-linux-arm64-musl": "16.3.8", + "@next/swc-linux-x64-gnu": "16.3.8", + "@next/swc-linux-x64-musl": "16.3.8", + "@next/swc-win32-arm64-msvc": "16.3.8", + "@next/swc-win32-x64-msvc": "16.3.8", + "sharp": "^0.35.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.7.tgz", + "integrity": "sha512-nS4mvFgVwUrecPTRdpdseM2fwpdfnX0/HibA8DgBRQtihUAzHQYJFU2l+OlEtiXtpqkrMvCPDwyXMUnASDt6Qw==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/redis": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.12.1.tgz", + "integrity": "sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.12.1", + "@redis/client": "5.12.1", + "@redis/json": "5.12.1", + "@redis/search": "5.12.1", + "@redis/time-series": "5.12.1" + }, + "engines": { + "node": ">= 18.19.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/ripemd160/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ripemd160/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/ripemd160/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ripemd160/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.1.tgz", + "integrity": "sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.71.1", + "@typescript-eslint/parser": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1", + "@typescript-eslint/utils": "8.71.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/webflow-api": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webflow-api/-/webflow-api-3.3.4.tgz", + "integrity": "sha512-GqKhOR4oUGsPX/gvOZIItBRLhCdNlPP3ind/PCR9Tj5YHeus5EWpamimiCW14SxHAa2BKQLJfxU1jvG6n5OLUw==", + "license": "MIT", + "dependencies": { + "crypto-browserify": "^3.12.1", + "form-data": "^4.0.4", + "formdata-node": "^6.0.3", + "node-fetch": "^2.7.0", + "readable-stream": "^4.5.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/data-client/package.json b/data-client/package.json index 420ef71..1e869a1 100644 --- a/data-client/package.json +++ b/data-client/package.json @@ -5,25 +5,31 @@ "scripts": { "dev": "tsx scripts/dev.ts", "build": "next build", - "start": "next start" + "start": "next start", + "lint": "eslint .", + "test": "node --import tsx --test tests/*.test.ts" }, "dependencies": { - "@ngrok/ngrok": "^1.4.1", - "@upstash/redis": "^1.36.2", - "chalk": "^5.3.0", - "jose": "^5.2.1", - "next": "^14.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "webflow-api": "^3.1.0" + "jose": "^6.2.0", + "next": "16.3.8", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "webflow-api": "3.3.4", + "redis": "^5.12.1" }, "devDependencies": { - "@types/node": "^20.11.16", - "@types/react": "^18.2.55", - "@types/react-dom": "^18.2.19", - "eslint": "^8.56.0", - "eslint-config-next": "^14.1.0", + "@types/node": "^24.0.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "eslint": "^9.39.0", "tsx": "^4.19.2", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "@ngrok/ngrok": "^1.4.1", + "chalk": "^5.3.0", + "@eslint/js": "^9.39.0", + "typescript-eslint": "^8.0.1" + }, + "engines": { + "node": ">=24" } } diff --git a/data-client/proxy.ts b/data-client/proxy.ts new file mode 100644 index 0000000..1fe9b5f --- /dev/null +++ b/data-client/proxy.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from "next/server"; + +export function proxy(request: NextRequest) { + const origin = request.headers.get("origin"); + const allowedOrigin = process.env.DESIGNER_EXTENSION_URI; + const response = request.method === "OPTIONS" + ? new NextResponse(null, { status: origin === allowedOrigin ? 204 : 403 }) + : NextResponse.next(); + response.headers.set("Vary", "Origin"); + response.headers.set("Cache-Control", "no-store"); + response.headers.set("X-Content-Type-Options", "nosniff"); + response.headers.set("X-Frame-Options", "DENY"); + response.headers.set("Referrer-Policy", "no-referrer"); + if (origin && origin === allowedOrigin) { + response.headers.set("Access-Control-Allow-Origin", origin); + response.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + response.headers.set("Access-Control-Allow-Headers", "Authorization, Content-Type"); + } + return response; +} + +export const config = { matcher: "/api/:path*" }; diff --git a/data-client/tests/security.test.ts b/data-client/tests/security.test.ts new file mode 100644 index 0000000..17658bd --- /dev/null +++ b/data-client/tests/security.test.ts @@ -0,0 +1,93 @@ +import { test, afterEach, mock } from "node:test"; +import assert from "node:assert/strict"; +import { runInNewContext } from "node:vm"; +import { NextRequest } from "next/server"; +import { SignJWT } from "jose"; +import db, { encrypt, decrypt } from "../app/lib/utils/database"; +import jwt from "../app/lib/utils/jwt"; +import { POST as exchange } from "../app/api/auth/token/route"; +import { GET as callback } from "../app/api/auth/callback/route"; +import { generateScript, type ScriptConfig } from "../app/lib/utils/scriptGenerator"; +import { checkScriptStatus } from "../app/lib/controllers/scriptController"; +import type { WebflowClient } from "webflow-api"; + +process.env.WEBFLOW_CLIENT_SECRET = "local-test-secret-not-used-in-production"; +afterEach(() => mock.restoreAll()); + +test("tokens are encrypted with random IVs and reject tampering", () => { + const token = "test-oauth-token"; + const encrypted = encrypt(token); + assert.equal(decrypt(encrypted), token); + assert.notEqual(encrypted, encrypt(token)); + assert.ok(!encrypted.includes(token)); + const parts = encrypted.split(":"); + parts[1] = "00".repeat(16); + assert.throws(() => decrypt(parts.join(":"))); +}); + +test("sessions can only retrieve their own site's token", async () => { + const get = mock.method(db, "getAccessTokenFromSiteId", async () => "access-token"); + const { sessionToken } = await jwt.createSessionToken("user-a", "site-a"); + const request = new NextRequest("http://localhost/api/scripts/remove", { headers: { Authorization: `Bearer ${sessionToken}` } }); + assert.equal(await jwt.verifyAuth(request, "site-b"), null); + assert.equal(get.mock.callCount(), 0); + assert.equal(await jwt.verifyAuth(request, "site-a"), "access-token"); + assert.equal(get.mock.calls[0].arguments[0], "site-a"); +}); + +test("expired and incorrectly signed sessions are rejected", async () => { + for (const secret of [process.env.WEBFLOW_CLIENT_SECRET!, "wrong-secret"]) { + const token = await new SignJWT({ siteId: "site-a" }).setProtectedHeader({ alg: "HS256" }) + .setSubject("user-a").setIssuer("simpleanalytics-webflow").setAudience("webflow-designer") + .setExpirationTime(secret === "wrong-secret" ? "1h" : 1) + .sign(new TextEncoder().encode(secret)); + await assert.rejects(jwt.verifySession(token)); + } +}); + +test("token exchange checks the site returned by Webflow and omits personal data", async () => { + mock.method(db, "getAccessTokenFromSiteId", async () => "access-token"); + const fetchMock = mock.method(globalThis, "fetch", async () => Response.json({ id: "user-a", siteId: "site-b", email: "private@example.invalid" })); + const request = () => new NextRequest("http://localhost/api/auth/token", { method: "POST", body: JSON.stringify({ siteId: "site-a", idToken: "id-token" }) }); + assert.equal((await exchange(request())).status, 401); + fetchMock.mock.mockImplementation(async () => Response.json({ id: "user-a", siteId: "site-a", email: "private@example.invalid" })); + const response = await exchange(request()); + assert.equal(response.status, 200); + const { sessionToken } = await response.json(); + assert.deepEqual(await jwt.verifySession(sessionToken), { userId: "user-a", siteId: "site-a" }); + assert.ok(!Buffer.from(sessionToken.split(".")[1], "base64url").toString().includes("email")); +}); + +test("OAuth rejects callbacks without a matching browser nonce before token exchange", async () => { + const fetchMock = mock.method(globalThis, "fetch", async () => { throw new Error("Must not exchange"); }); + for (const cookie of ["", "webflow_oauth_state=" + "b".repeat(64)]) { + const request = new NextRequest(`http://localhost/api/auth/callback?code=example&state=${"a".repeat(64)}`, { headers: { cookie } }); + assert.equal((await callback(request)).status, 400); + } + assert.equal(fetchMock.mock.callCount(), 0); +}); + +const config: ScriptConfig = { + trackPageviews: true, trackEvents: false, collectDNT: false, automatedEvents: true, + hashMode: false, ignorePages: "", customDomain: "", overwriteDomain: "", + collectOutbound: false, collectEmails: false, collectDownloads: false, + downloadExtensions: "", usePageTitle: true, collectFullUrls: false, collectLinkEvents: false, +}; + +test("script settings are serialized safely and disabled event types stay disabled", () => { + const scripts: Array<{ src: string; dataset: Record }> = []; + const value = '";globalThis.injected=true;//'; + const source = generateScript({ ...config, ignorePages: value }); + const context = { document: { createElement: () => ({ dataset: {} }), head: { appendChild: (script: typeof scripts[number]) => scripts.push(script) } } }; + runInNewContext(source, context); + assert.equal(scripts.length, 1); + assert.equal(scripts[0].dataset.ignorePages, value); + assert.ok(!source.includes("")); + assert.ok(!("injected" in context)); + assert.throws(() => generateScript({ ...config, customDomain: 'example.com/path' })); +}); + +test("a registered script is not installed after it has been removed", async () => { + const webflow = { scripts: { list: async () => ({ registeredScripts: [{ id: "sa", displayName: "Simple Analytics", version: "1.0.0" }] }) }, sites: { scripts: { getCustomCode: async () => ({ scripts: [] }) } } } as unknown as WebflowClient; + assert.equal((await checkScriptStatus(webflow, "site-a")).installed, false); +}); diff --git a/data-client/tsconfig.json b/data-client/tsconfig.json index 9111463..9560a57 100644 --- a/data-client/tsconfig.json +++ b/data-client/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -11,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { @@ -19,7 +23,9 @@ } ], "paths": { - "@/*": ["./*"] + "@/*": [ + "./*" + ] } }, "include": [ @@ -27,7 +33,10 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - "scripts/dev.ts" + "scripts/dev.ts", + ".next/dev/types/**/*.ts" ], - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] } diff --git a/designer-extension/.env.prod b/designer-extension/.env.prod index a647b9d..7181ffb 100644 --- a/designer-extension/.env.prod +++ b/designer-extension/.env.prod @@ -1 +1 @@ -VITE_DATA_CLIENT_URL=https://simpleanalyticsapp.vercel.app +VITE_DATA_CLIENT_URL=https://webflow.simpleanalytics.com diff --git a/designer-extension/.gitignore b/designer-extension/.gitignore index a547bf3..c9a6b8b 100644 --- a/designer-extension/.gitignore +++ b/designer-extension/.gitignore @@ -22,3 +22,5 @@ dist-ssr *.njsproj *.sln *.sw? +bundle.zip +*.tsbuildinfo diff --git a/designer-extension/bundle.zip b/designer-extension/bundle.zip deleted file mode 100644 index 7951630ea4ea1e9cc6618376c780904fe62cae90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66731 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ⅇ!fgY7)39ZB5AN4vL7mq8Q_~w}OniOE%MTZv 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" } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "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.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -46,9 +50,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "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": { @@ -56,21 +60,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -87,14 +91,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -104,14 +108,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@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" @@ -121,9 +125,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -131,29 +135,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -163,9 +167,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "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": { @@ -173,9 +177,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "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==", "dev": true, "license": "MIT", "engines": { @@ -183,9 +187,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "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": { @@ -193,9 +197,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -203,27 +207,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -265,33 +269,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@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.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -299,23 +303,23 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], @@ -326,13 +330,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -343,13 +347,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -360,13 +364,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -377,13 +381,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -394,13 +398,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -411,13 +415,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -428,13 +432,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -445,13 +449,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -462,13 +466,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -479,13 +483,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -496,13 +500,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -513,13 +517,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -530,13 +534,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -547,13 +551,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -564,13 +568,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -581,13 +585,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -598,13 +602,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], @@ -615,13 +636,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -632,13 +670,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -649,13 +704,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -666,13 +721,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -683,13 +738,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -700,7 +755,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -861,29 +916,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -962,17 +1031,34 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "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==", + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", "dev": true, "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.0.tgz", + "integrity": "sha512-FLLpX941CD/7aBvX3GqLGBM9OvY5PGXTryPJkG5r5/OuBpZpV5k45Uo3RxAvqd35tsyc3hH1bhtz921IKzoIzg==", "cpu": [ "arm" ], @@ -984,9 +1070,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.0.tgz", + "integrity": "sha512-iI32XM/jnDYJsvx5ai/za/7gWO9XoSeFFtU06/ZCaRgyilfJOjaopD3nOpk2WrTCGOyeDt4hleUiGOAnd5tHNg==", "cpu": [ "arm64" ], @@ -998,9 +1084,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.0.tgz", + "integrity": "sha512-dRBZlbt2sEtF8Mn4MODThze9fIPdDoOhVSlXMAKy5dQbOYK9pOOoPY4Fat3OTtr0+8zbJo5TTZwlsfxY/kDNJg==", "cpu": [ "arm64" ], @@ -1012,9 +1098,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.0.tgz", + "integrity": "sha512-Y3whrJOMucfkhgNkvcM93M6j8C53iEAMmPMYD99C6G1kUy4ELigLWk9ar2/4aCBSRgsql0zdlOemLgW2m5GZgQ==", "cpu": [ "x64" ], @@ -1026,9 +1112,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.0.tgz", + "integrity": "sha512-S6S1GEzPjFF6IONYJX3AKt/799+8RgBgCcwRnUo+Ep1IvBxUbw5M51iU0TDdK2NWWVnSvCQ8yi6Em0fGASMOng==", "cpu": [ "arm64" ], @@ -1040,9 +1126,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.0.tgz", + "integrity": "sha512-Ed1DPoFENkXj4IGzSB12SesV40iP6LxCCrbTIkSGIQBpOOxpBM+0kj62lmaeIHfMWTDoddqTOtqc7s8LYfn+Pw==", "cpu": [ "x64" ], @@ -1054,9 +1140,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.0.tgz", + "integrity": "sha512-J/6xhutJPtFyIkmHdf4olOdHHKH5kOJNumojdWn+Elv5ICEYTVOa362Vw27TxsK0BLzb1le3EDlO3UbvFK1lnQ==", "cpu": [ "arm" ], @@ -1068,9 +1154,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.0.tgz", + "integrity": "sha512-G4L/Nnx59d2LvDoV/P4S5lZ7oVLT6hodOHMacqjiq2LL2gts86KbkEBTYL53+fBt0odhf7XPIbIATGoYiJctxg==", "cpu": [ "arm" ], @@ -1082,9 +1168,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.0.tgz", + "integrity": "sha512-zF16pvJDEUksGYRbfnXne7pdklAyj2XDmJeYKgP93oq0cUyX9Y8O7lUo1R+IY1GagP/YBvCEDCe7eEcAKpw//g==", "cpu": [ "arm64" ], @@ -1096,9 +1182,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.0.tgz", + "integrity": "sha512-nIggWn8n+w12+R5lsV+PYYoUY8vuObxPAQqJI7WES+9v0qspYuAHoZMfYM0yLxlipasT5BY9ht5W8t5c/rSCkg==", "cpu": [ "arm64" ], @@ -1110,9 +1196,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.0.tgz", + "integrity": "sha512-WLOTiHPzszP0dxIMsHpFeDSOSB5I49Mj8K4+yKE3H1X/D0JRG5XZ5jrctJLMjnUc/LUzaKiwq6EHWNJ8pup8PA==", "cpu": [ "loong64" ], @@ -1124,9 +1210,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.0.tgz", + "integrity": "sha512-35RK9aRhh7g7Pmeu5gQ01aCvrhXzj9LoBE6xtUcEH1ns+nyTaSzxSpADmBQINu4QiTDGvmQYwo1j5hlg1rRXFw==", "cpu": [ "loong64" ], @@ -1138,9 +1224,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.0.tgz", + "integrity": "sha512-RqTfnpoTjuWfPAmqK5p2edt2AMCDJWm6oTn7sKfmyx+6tRga0ip05SJ977o6udTcIiDeF/CJMUfkaN4gtWt6Aw==", "cpu": [ "ppc64" ], @@ -1152,9 +1238,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.0.tgz", + "integrity": "sha512-RQjZPigjxFvFZDijG6rjlSXxhRaTf9zLYVpGK+lVcga64oMqP1GbgVqCb46eHTTceSIgp89Ja0y5aPe3GFBYHg==", "cpu": [ "ppc64" ], @@ -1166,9 +1252,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.0.tgz", + "integrity": "sha512-tjL58mgQcTRnDRpeky+Sth1MKQeYi2Ct6r3Luq9E5aTSjnM/i/JbozqZkKqp9thM5+oDSZxo+05f9kdQedfTGA==", "cpu": [ "riscv64" ], @@ -1180,9 +1266,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.0.tgz", + "integrity": "sha512-ictoRyVaoZ8C67myHef+8hhCa6IplXJJ/xUCkVCxziV4zxMZh+jvhkhGsPBLqj1S72fnqD0QH5zL4lcaa4X7DQ==", "cpu": [ "riscv64" ], @@ -1194,9 +1280,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.0.tgz", + "integrity": "sha512-8aVvZ2hfr9RLFzBQkQNmYeHLDja8fLeQAmLt757zdKN/B+8L809KlLJKctQjS22wZotptV8G6o7fVPuAs8S1tQ==", "cpu": [ "s390x" ], @@ -1208,9 +1294,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.0.tgz", + "integrity": "sha512-2dEF8GAcDKwshUfydD+GhosppNyBzhVUHkdFF3CXd3FpOkzj/RZzF5aoEtzklujklT5qFVRU2GK/cFeLaTNKVg==", "cpu": [ "x64" ], @@ -1222,9 +1308,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.0.tgz", + "integrity": "sha512-3XHTvoo2hKkh0MiOfS7SkhHjMaaWF7P4F3drocZZaho/Z8w7h/jpnuBHm91BOQLfvrKHs1yR+zir4E8RI3XzTQ==", "cpu": [ "x64" ], @@ -1236,9 +1322,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.0.tgz", + "integrity": "sha512-HKtkzodL4a3KFWDXDoO61cRynAWTR3LnAE0oGtfG9lcNFpNuVYUFOByxDgNqEvic9rvaQbAj+SFwTEWDptRaIw==", "cpu": [ "x64" ], @@ -1250,9 +1336,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.0.tgz", + "integrity": "sha512-JBsxqiY16uyyCujtoC3s7zL+SrYyNElptD7K4V+KUoJ2V8/XG3hPWpheK+SuPwNh1cikJvIZmIdsrYXvD6PLJw==", "cpu": [ "arm64" ], @@ -1264,9 +1350,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.0.tgz", + "integrity": "sha512-S5Qrijh37qhnUYQ0ay8WN3LuWeKjow7wTUyCOPMZtnXiBXYXdsZETI6ZLfYnQAKovltckPLBFdNeD61P2rJc7g==", "cpu": [ "arm64" ], @@ -1278,9 +1364,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.0.tgz", + "integrity": "sha512-zbZ6iMmLFEsCtTdc2/y1F/cDFVNNCPF7Ae8PCT/wdPE//hGwYp6r+MyCLMFX+kOeIe09Pz6ASfnnfpSTN1FJQQ==", "cpu": [ "ia32" ], @@ -1292,9 +1378,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.0.tgz", + "integrity": "sha512-L6Hw69oNpuYaOr1zCG30v0/DqKYCSRwodPGwIcVZ/6f8SOzq9rxtL/xNVI5MO4wjli+Ko0ErK9uHJL3mNPt8wQ==", "cpu": [ "x64" ], @@ -1306,9 +1392,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.0.tgz", + "integrity": "sha512-nl+CTWIgOGszQmUz/kanfgrfFZ4qiwxKIfT/ryl3N3Ewk5tqizmYtNXiqb7Bu/fDtNgu1kyxdFiAUJNur3iysg==", "cpu": [ "x64" ], @@ -1391,9 +1477,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1631,9 +1717,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { @@ -1641,13 +1727,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -1712,24 +1798,24 @@ } }, "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==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", + "@babel/core": "^7.29.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", + "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "react-refresh": "^0.18.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@webflow/designer-extension-typings": { @@ -1740,9 +1826,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, "license": "MIT", "bin": { @@ -1763,9 +1849,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1795,20 +1881,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1824,32 +1896,22 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.11.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", + "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "baseline-browser-mapping": "dist/cli.cjs" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=6.0.0" } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -1857,23 +1919,10 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", "dev": true, "funding": [ { @@ -1891,11 +1940,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -1915,9 +1964,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "version": "1.0.30001814", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz", + "integrity": "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==", "dev": true, "funding": [ { @@ -1953,28 +2002,19 @@ } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/color-convert": { @@ -2059,16 +2099,16 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "version": "1.5.445", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.445.tgz", + "integrity": "sha512-iX7o1nF+qNb3Bv5mLYgqfgF9Znh9e/8MKXdmbb8Gv2Q47OKIzTwEPD1oda1HrgEwYatTLJj0wQjHvIFt8Db53Q==", "dev": true, "license": "ISC" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2076,32 +2116,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escalade": { @@ -2338,6 +2381,31 @@ "dev": true, "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2351,19 +2419,6 @@ "node": ">=16.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2396,9 +2451,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -2427,19 +2482,6 @@ "node": ">=6.9.0" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/globals": { "version": "15.15.0", "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", @@ -2500,19 +2542,6 @@ "node": ">=0.8.19" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2536,16 +2565,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2560,10 +2579,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -2698,9 +2727,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -2718,9 +2747,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -2744,20 +2773,13 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "version": "2.0.57", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz", + "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, "node_modules/optionator": { @@ -2851,22 +2873,22 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", "dev": true, "funding": [ { @@ -2884,9 +2906,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.19", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -2938,9 +2960,9 @@ } }, "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==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "dev": true, "license": "MIT", "engines": { @@ -2948,16 +2970,17 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/resolve-from": { @@ -2971,13 +2994,13 @@ } }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.0.tgz", + "integrity": "sha512-gjA/kFeDfffII8COoPsvLPvE16hWDTEc1+0QKQNKUkuV8iJjqsC4xXs8/FzuL0v9EV39kuxTbAgcFoTRyy2TGQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -2987,31 +3010,32 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.0", + "@rollup/rollup-android-arm64": "4.64.0", + "@rollup/rollup-darwin-arm64": "4.64.0", + "@rollup/rollup-darwin-x64": "4.64.0", + "@rollup/rollup-freebsd-arm64": "4.64.0", + "@rollup/rollup-freebsd-x64": "4.64.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.0", + "@rollup/rollup-linux-arm-musleabihf": "4.64.0", + "@rollup/rollup-linux-arm64-gnu": "4.64.0", + "@rollup/rollup-linux-arm64-musl": "4.64.0", + "@rollup/rollup-linux-loong64-gnu": "4.64.0", + "@rollup/rollup-linux-loong64-musl": "4.64.0", + "@rollup/rollup-linux-ppc64-gnu": "4.64.0", + "@rollup/rollup-linux-ppc64-musl": "4.64.0", + "@rollup/rollup-linux-riscv64-gnu": "4.64.0", + "@rollup/rollup-linux-riscv64-musl": "4.64.0", + "@rollup/rollup-linux-s390x-gnu": "4.64.0", + "@rollup/rollup-linux-x64-gnu": "4.64.0", + "@rollup/rollup-linux-x64-musl": "4.64.0", + "@rollup/rollup-openbsd-x64": "4.64.0", + "@rollup/rollup-openharmony-arm64": "4.64.0", + "@rollup/rollup-win32-arm64-msvc": "4.64.0", + "@rollup/rollup-win32-ia32-msvc": "4.64.0", + "@rollup/rollup-win32-x64-gnu": "4.64.0", + "@rollup/rollup-win32-x64-msvc": "4.64.0", "fsevents": "~2.3.2" } }, @@ -3058,9 +3082,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3110,50 +3134,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -3226,9 +3206,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", "dev": true, "funding": [ { @@ -3267,21 +3247,24 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz", + "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -3290,19 +3273,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -3323,6 +3312,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, diff --git a/designer-extension/package.json b/designer-extension/package.json index 5064f8e..7f99d5b 100644 --- a/designer-extension/package.json +++ b/designer-extension/package.json @@ -5,10 +5,11 @@ "type": "module", "scripts": { "dev": "vite", - "build:prod": "tsc && vite build --mode prod && webflow extension bundle && mv bundle.zip bundle/prod/bundle_$(date +'%Y-%m-%d_%H-%M-%S').zip", - "build:dev": "tsc && vite build --mode development && webflow extension bundle && mv bundle.zip bundle/development/bundle_$(date +'%Y-%m-%d_%H-%M-%S').zip", + "build:prod": "npm run build && node scripts/bundle.mjs", + "build:dev": "tsc -b && vite build --mode development && node scripts/bundle.mjs", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "build": "tsc -b && vite build --mode prod" }, "dependencies": { "@tanstack/react-query": "^5.56.2", @@ -21,15 +22,19 @@ "@types/node": "^22.5.5", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", + "@vitejs/plugin-react": "^5.2.0", "@webflow/designer-extension-typings": "*", - "chokidar": "^3.6.0", + "chokidar": "^4.0.3", "eslint": "^9.9.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", - "vite": "^5.4.1" + "vite": "^7.3.7", + "fflate": "^0.8.3" + }, + "engines": { + "node": ">=24" } } diff --git a/designer-extension/scripts/bundle.mjs b/designer-extension/scripts/bundle.mjs new file mode 100644 index 0000000..3087978 --- /dev/null +++ b/designer-extension/scripts/bundle.mjs @@ -0,0 +1,24 @@ +import { readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { join, extname } from "node:path"; +import { zipSync } from "fflate"; + +// Webflow's bundle format is the public directory contents plus webflow.json. +// Package only built assets; never include source, environment files or secrets. +const files = { "webflow.json": new Uint8Array(readFileSync("webflow.json")) }; +const config = JSON.parse(readFileSync("webflow.json", "utf8")); +const allowed = new Set([".html", ".js", ".css", ".json", ".svg", ".png", ".jpg", ".ico", ".woff", ".woff2"]); +function collect(directory, prefix = "") { + for (const entry of readdirSync(directory, { withFileTypes: true })) { + const name = prefix + entry.name; + const path = join(directory, entry.name); + if (entry.isDirectory()) collect(path, `${name}/`); + else if (entry.isFile() && allowed.has(extname(name))) files[name] = new Uint8Array(readFileSync(path)); + else throw new Error(`Unexpected bundle entry: ${name}`); + } +} +collect(config.publicDir); +if (!files["index.html"]) throw new Error("Missing extension index.html"); +const bundle = zipSync(files, { level: 9 }); +if (bundle.length >= 5 * 1024 * 1024) throw new Error("Webflow bundles must be smaller than 5 MB"); +writeFileSync("bundle.zip", bundle); +console.log(`Created bundle.zip (${bundle.length} bytes)`); diff --git a/designer-extension/src/App.tsx b/designer-extension/src/App.tsx index f133c0d..f231811 100644 --- a/designer-extension/src/App.tsx +++ b/designer-extension/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useEffect } from "react"; import { useAuth } from "./hooks/useAuth"; import { AuthScreen } from "./components/AuthScreen"; import { ConfigPanel } from "./components/ConfigPanel"; @@ -6,24 +6,12 @@ import "./App.css"; function App() { const { sessionToken, isAuthLoading, exchangeAndVerifyIdToken, logout } = useAuth(); - const hasCheckedToken = useRef(false); useEffect(() => { webflow.setExtensionSize("comfortable"); - if (!hasCheckedToken.current) { - const storedUser = localStorage.getItem("wf_hybrid_user"); - const wasLoggedOut = localStorage.getItem("explicitly_logged_out"); - - if (storedUser && !wasLoggedOut) { - exchangeAndVerifyIdToken(); - } - hasCheckedToken.current = true; - } - const handleAuthComplete = async (event: MessageEvent) => { - if (event.data === "authComplete") { - localStorage.removeItem("explicitly_logged_out"); + if (event.origin === new URL(import.meta.env.VITE_DATA_CLIENT_URL).origin && event.data === "authComplete") { await exchangeAndVerifyIdToken(); } }; @@ -31,7 +19,6 @@ function App() { window.addEventListener("message", handleAuthComplete); return () => { window.removeEventListener("message", handleAuthComplete); - hasCheckedToken.current = false; }; }, [exchangeAndVerifyIdToken]); diff --git a/designer-extension/src/components/AuthScreen.tsx b/designer-extension/src/components/AuthScreen.tsx index 9f1524a..322ea93 100644 --- a/designer-extension/src/components/AuthScreen.tsx +++ b/designer-extension/src/components/AuthScreen.tsx @@ -13,18 +13,14 @@ export function AuthScreen({ onAuth }: AuthScreenProps) { setError(null); try { - const response = await fetch( - `${BASE_URL}/api/auth/authorize?state=webflow_designer&json=true` - ); - - if (!response.ok) { - throw new Error(`Server returned ${response.status}`); + // Open synchronously so the browser permits the popup and the backend + // can set its state cookie before redirecting to Webflow. + const authWindow = window.open(`${BASE_URL}/api/auth/authorize?popup=true`, "_blank", "width=600,height=600"); + if (!authWindow) { + setError("Allow popups to connect to Webflow, then try again."); + return; } - const { url } = await response.json(); - - const authWindow = window.open(url, "_blank", "width=600,height=600"); - const checkWindow = setInterval(() => { if (authWindow?.closed) { clearInterval(checkWindow); diff --git a/designer-extension/src/hooks/useAuth.ts b/designer-extension/src/hooks/useAuth.ts index 49b7683..8b3d26b 100644 --- a/designer-extension/src/hooks/useAuth.ts +++ b/designer-extension/src/hooks/useAuth.ts @@ -1,213 +1,54 @@ -import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query"; +import { useCallback, useEffect, useRef, useState } from "react"; import { jwtDecode } from "jwt-decode"; -import { User, DecodedToken } from "../types/types"; -const base_url = import.meta.env.VITE_DATA_CLIENT_URL; +const baseUrl = import.meta.env.VITE_DATA_CLIENT_URL; +interface Session { siteId: string; exp: number; } -interface AuthState { - user: User; - sessionToken: string; -} - -/** - * Custom hook for managing authentication state and token exchange. - * - * Authentication Flow: - * 1. User initiates auth -> exchangeAndVerifyIdToken() - * - Gets ID token from Webflow (Designer APIs) - * - Exchanges it for a session token via API - * - * 2. Token Exchange -> tokenMutation - * - Sends ID token to Data Client - * - Data Client validates and returns session token - * - On success, decodes and stores token + user data - * - * 3. Session Management -> useQuery for token validation - * - Automatically checks for existing valid session - * - Handles token expiration - * - Manages loading states - * - * @returns {Object} Authentication utilities and state - * - user: Current user information - * - sessionToken: Active session token - * - isAuthLoading: Loading state - * - exchangeAndVerifyIdToken: Exchange ID token for session token - * - logout: Clear authentication state - */ export function useAuth() { - const queryClient = useQueryClient(); - const isExchangingToken = { current: false }; - - // Query for managing auth state and token validation - const { data: authState, isLoading: isAuthLoading } = useQuery({ - queryKey: ["auth"], - queryFn: async () => { - const storedUser = localStorage.getItem("wf_hybrid_user"); - const wasExplicitlyLoggedOut = localStorage.getItem( - "explicitly_logged_out" - ); - - // Return initial state if no stored user or logged out - if (!storedUser || wasExplicitlyLoggedOut) { - return { user: { firstName: "", email: "" }, sessionToken: "" }; - } - - try { - const userData = JSON.parse(storedUser); - if (!userData.sessionToken) { - return { user: { firstName: "", email: "" }, sessionToken: "" }; - } - - // Decode and validate token - const decodedToken = jwtDecode(userData.sessionToken) as DecodedToken; - if (decodedToken.exp * 1000 <= Date.now()) { - // Token expired - clear storage - localStorage.removeItem("wf_hybrid_user"); - return { user: { firstName: "", email: "" }, sessionToken: "" }; - } - - // Return valid auth state - return { - user: { - firstName: decodedToken.user.firstName, - email: decodedToken.user.email, - }, - sessionToken: userData.sessionToken, - }; - } catch (error) { - console.error("Error parsing stored user data:", error); - // Clear invalid data - localStorage.removeItem("wf_hybrid_user"); - return { user: { firstName: "", email: "" }, sessionToken: "" }; - } - }, - staleTime: Infinity, // Never consider the data stale - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - gcTime: 1000 * 60 * 60, // Cache for 1 hour - }); - - // Mutation for exchanging ID token for session token - const tokenMutation = useMutation({ - mutationFn: async (idToken: string) => { - // Get site info from Webflow - const siteInfo = await webflow.getSiteInfo(); + const [sessionToken, setSessionToken] = useState(""); + const [isAuthLoading, setIsAuthLoading] = useState(true); + const exchanging = useRef(false); - // Exchange token with backend - const response = await fetch(`${base_url}/api/auth/token`, { + const exchangeAndVerifyIdToken = useCallback(async () => { + if (exchanging.current) return; + exchanging.current = true; + try { + const { siteId } = await webflow.getSiteInfo(); + const idToken = await webflow.getIdToken(); + const response = await fetch(`${baseUrl}/api/auth/token`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ idToken: idToken, siteId: siteInfo.siteId }), + body: JSON.stringify({ siteId, idToken }), + signal: AbortSignal.timeout(15000), }); - - if (!response.ok) { - const errorData = await response.json(); - throw new Error( - `Failed to exchange token: ${JSON.stringify(errorData)}` - ); - } - + if (!response.ok) throw new Error("Authorization failed"); const data = await response.json(); - if (!data.sessionToken) { - throw new Error("No session token received"); - } - - return data; - }, - onSuccess: (data) => { - try { - // Decode the new token - const decodedToken = jwtDecode(data.sessionToken) as DecodedToken; - const userData = { - sessionToken: data.sessionToken, - firstName: decodedToken.user.firstName, - email: decodedToken.user.email, - exp: decodedToken.exp, - }; - - // Update localStorage - localStorage.setItem("wf_hybrid_user", JSON.stringify(userData)); - localStorage.removeItem("explicitly_logged_out"); - - // Directly update the query data instead of invalidating - queryClient.setQueryData(["auth"], { - user: { - firstName: decodedToken.user.firstName, - email: decodedToken.user.email, - }, - sessionToken: data.sessionToken, - }); - } catch (error) { - console.error("Error decoding token:", error); - } - }, - }); - - // Function to initiate token exchange process - const exchangeAndVerifyIdToken = async () => { - // Check if we already have a valid session token - const storedUser = localStorage.getItem("wf_hybrid_user"); - if (storedUser) { - try { - const userData = JSON.parse(storedUser); - if (userData.sessionToken) { - const decodedToken = jwtDecode(userData.sessionToken) as DecodedToken; - if (decodedToken.exp * 1000 > Date.now()) { - return; - } - } - } catch (error) { - console.error("Error checking stored token:", error); - } - } - - if (isExchangingToken.current) { - console.log("Token exchange already in progress"); - return; - } - - try { - isExchangingToken.current = true; - // Small delay to prevent rapid retries - await new Promise((resolve) => setTimeout(resolve, 1000)); - - // Get new ID token from Webflow - const idToken = await webflow.getIdToken(); - - // Validate token format - if (!idToken || typeof idToken !== "string" || !idToken.trim()) { - throw new Error("Invalid or missing ID token"); + const session = jwtDecode(data.sessionToken); + if (session.siteId !== siteId || session.exp * 1000 <= Date.now()) { + throw new Error("Invalid session"); } - - // Exchange token using mutation - await tokenMutation.mutateAsync(idToken); - } catch (error) { - console.error("Detailed error in token exchange:", error); - // Clear storage on error to force re-auth - localStorage.removeItem("wf_hybrid_user"); + setSessionToken(data.sessionToken); + } catch { + setSessionToken(""); } finally { - isExchangingToken.current = false; + exchanging.current = false; + setIsAuthLoading(false); } - }; + }, []); - // Function to handle user logout - const logout = () => { - // Set logout flag and clear storage - localStorage.setItem("explicitly_logged_out", "true"); + useEffect(() => { + // Discard personal data and sessions left by the previous app version. localStorage.removeItem("wf_hybrid_user"); - queryClient.setQueryData(["auth"], { - user: { firstName: "", email: "" }, - sessionToken: "", - }); - queryClient.clear(); - }; - - return { - user: authState?.user || { firstName: "", email: "" }, - sessionToken: authState?.sessionToken || "", - isAuthLoading, - exchangeAndVerifyIdToken, - logout, - }; + localStorage.removeItem("explicitly_logged_out"); + void exchangeAndVerifyIdToken(); + }, [exchangeAndVerifyIdToken]); + + useEffect(() => { + if (!sessionToken) return; + const delay = Math.max(0, jwtDecode(sessionToken).exp * 1000 - Date.now() - 60000); + const timer = setTimeout(exchangeAndVerifyIdToken, delay); + return () => clearTimeout(timer); + }, [sessionToken, exchangeAndVerifyIdToken]); + + return { sessionToken, isAuthLoading, exchangeAndVerifyIdToken, logout: () => setSessionToken("") }; } diff --git a/designer-extension/vite.config.ts b/designer-extension/vite.config.ts index c498090..adba6a9 100644 --- a/designer-extension/vite.config.ts +++ b/designer-extension/vite.config.ts @@ -94,7 +94,7 @@ export default defineConfig({ }, plugins: [ react(), - wfDesignerExtensionPlugin(['../data-client/app/api/**/*.ts']), + wfDesignerExtensionPlugin(['../data-client/app/api']), ], root: './', base: './', diff --git a/package-lock.json b/package-lock.json index 0067025..4a44757 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,9 @@ "license": "MIT", "dependencies": { "concurrently": "^9.1.0" + }, + "engines": { + "node": ">=24" } }, "node_modules/ansi-regex": { @@ -164,9 +167,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "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/require-directory": { @@ -188,10 +191,13 @@ } }, "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", + "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } diff --git a/package.json b/package.json index 7705a8c..ea55da9 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,14 @@ "description": "Simple Analytics Webflow Hybrid App", "scripts": { "dev": "concurrently \"cd data-client && npm run dev\" \"cd designer-extension && npm run dev\"", - "install:all": "cd data-client && npm install && cd ../designer-extension && npm install", - "build": "cd data-client && npm run build && cd ../designer-extension && npm run build:dev" + "install:all": "npm ci --prefix data-client && npm ci --prefix designer-extension", + "build": "npm run build --prefix data-client && npm run build --prefix designer-extension" }, "license": "MIT", "dependencies": { "concurrently": "^9.1.0" + }, + "engines": { + "node": ">=24" } } diff --git a/scripts/smoke.mjs b/scripts/smoke.mjs new file mode 100644 index 0000000..b8a8ac4 --- /dev/null +++ b/scripts/smoke.mjs @@ -0,0 +1,37 @@ +import assert from "node:assert/strict"; + +const base = process.env.SMOKE_BASE_URL || "http://127.0.0.1:3039"; +const origin = "https://smoke-test.webflow-ext.com"; +const request = (path, options) => fetch(base + path, { signal: AbortSignal.timeout(5000), ...options }); + +if (process.argv.includes("--unhealthy")) { + const response = await request("/api/health"); + assert.equal(response.status, 503); + assert.deepEqual(await response.json(), { status: "unhealthy" }); +} else { + const health = await request("/api/health"); + assert.equal(health.status, 200); + assert.deepEqual(await health.json(), { status: "healthy" }); + for (const action of ["register", "apply", "remove"]) { + const response = await request(`/api/scripts/${action}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ siteId: "test-site", scriptId: "test-script", version: "1.0.0" }) }); + assert.equal(response.status, 401); + } + for (const path of ["/api/sites", "/api/scripts/status?siteId=test-site"]) { + assert.equal((await request(path)).status, 401); + } + const preflight = await request("/api/scripts/apply", { method: "OPTIONS", headers: { Origin: origin, "Access-Control-Request-Method": "POST" } }); + assert.equal(preflight.status, 204); + assert.equal(preflight.headers.get("access-control-allow-origin"), origin); + const denied = await request("/api/scripts/apply", { method: "OPTIONS", headers: { Origin: "https://untrusted.example" } }); + assert.equal(denied.status, 403); + assert.equal(denied.headers.get("access-control-allow-origin"), null); + const auth = await request("/api/auth/authorize?popup=true", { redirect: "manual" }); + assert.equal(auth.status, 307); + const url = new URL(auth.headers.get("location")); + assert.equal(url.origin, "https://webflow.com"); + assert.match(url.searchParams.get("state"), /^[a-f0-9]{64}$/); + assert.match(auth.headers.get("set-cookie"), /HttpOnly/i); + const callback = await request(`/api/auth/callback?code=test&state=${url.searchParams.get("state")}`); + assert.equal(callback.status, 400); +} +console.log("Backend smoke checks passed"); From 55694a605ee831594ae4b3e628ac22d32a33542e Mon Sep 17 00:00:00 2001 From: Simple Analytics Codex Date: Wed, 7 Oct 2026 15:30:17 +0200 Subject: [PATCH 3/3] Fix Webflow PR review findings --- README.md | 146 ++++++++++-------- data-client/app/api/auth/callback/route.ts | 16 +- data-client/app/api/auth/disconnect/route.ts | 73 +++++++++ .../app/lib/controllers/scriptController.ts | 46 ++---- data-client/app/lib/utils/database.ts | 97 +++++++++++- data-client/tests/security.test.ts | 33 +++- designer-extension/src/App.css | 5 + designer-extension/src/App.tsx | 4 +- .../src/components/ConfigPanel.tsx | 94 ++++++++--- designer-extension/src/hooks/useAuth.ts | 7 +- .../src/hooks/useInstallScript.ts | 3 - designer-extension/src/services/api.ts | 17 ++ designer-extension/src/types/types.ts | 1 - scripts/smoke.mjs | 2 + 14 files changed, 409 insertions(+), 135 deletions(-) create mode 100644 data-client/app/api/auth/disconnect/route.ts diff --git a/README.md b/README.md index d27fddb..0a2bb25 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,130 @@ -# Simple Analytics — Webflow Hybrid App +# Simple Analytics Webflow App -A Webflow Hybrid App that lets site owners add [Simple Analytics](https://www.simpleanalytics.com/) to their Webflow sites directly from the Webflow Designer. It uses the Webflow Data API to inject the Simple Analytics tracking script via Custom Code, and the Designer API to authenticate users. +A Webflow Hybrid App that lets site owners add and configure +[Simple Analytics](https://www.simpleanalytics.com/) from the Webflow Designer. +The Designer Extension provides the interface; the Next.js data client handles +OAuth and manages the app's custom code through the Webflow Data API. ## Architecture -``` -designer-extension/ Vite + React frontend (runs inside Webflow Designer) - ├── src/App.tsx Entry point — auth gate + config panel - ├── src/hooks/useAuth.ts OAuth token exchange via ID tokens - └── src/services/api.ts Calls to the data-client API - -data-client/ Next.js backend (API + OAuth server) - ├── app/api/auth/ OAuth authorize / callback / token routes - ├── app/api/scripts/ Register + apply Simple Analytics script - └── app/lib/utils/ Database (SQLite), JWT, ngrok helper +```text +designer-extension/ Vite + React extension shown in Webflow Designer + src/App.tsx Authentication gate and configuration panel + src/hooks/useAuth.ts Designer ID-token exchange + src/services/api.ts Calls to the data-client API + +data-client/ Next.js API and OAuth service + app/api/auth/ OAuth, session, and disconnect routes + app/api/scripts/ Register, apply, inspect, and remove app scripts + app/lib/utils/database.ts Encrypted OAuth-token storage in Redis ``` ## Prerequisites -- Node.js 18+ -- A [Webflow](https://webflow.com) site and workspace -- A registered Webflow App (see Setup step 2) +- Node.js 24 or newer +- Redis 7 +- A Webflow site and workspace +- A registered Webflow App -## Setup +## Local setup -1. **Clone the repository:** +1. Clone this repository and install the locked dependencies: ```bash - git clone https://github.com/runclubs/Simple-Analytics-App.git - cd Simple-Analytics-App + git clone https://github.com/simpleanalytics/webflow-app.git + cd webflow-app + npm ci + npm run install:all ``` -2. **Install all dependencies:** +2. Start a local Redis instance. This example exposes Redis only on localhost: ```bash - npm install - npm run install:all + docker run --rm --name webflow-app-redis -p 127.0.0.1:6379:6379 redis:7.4.8 ``` - This installs the root dependencies (concurrently), plus all packages for both `data-client/` and `designer-extension/`. +3. Register a Webflow App and configure this redirect URI: -3. **Register a Webflow App** in [your Workspace settings](https://developers.webflow.com/v2.0.0/data/docs/register-an-app): + ```text + http://localhost:3001/api/auth/callback + ``` - - Redirect URI: `http://localhost:3001/api/auth/callback` - - Required scopes (all four must be enabled): - - `authorized_user:read` - - `sites:read` - - `custom_code:read` - - `custom_code:write` + Enable these scopes: - > **Note:** if you forget to enable `authorized_user:read`, the OAuth flow will fail with an `invalid_scope` error. + - `authorized_user:read` + - `sites:read` + - `custom_code:read` + - `custom_code:write` -4. **Create the environment file:** +4. Create the backend environment file: ```bash cp data-client/.env.example data-client/.env ``` - Open `data-client/.env` and fill in your credentials from the Webflow Dashboard (Workspace Settings → Apps & Integrations → Your App): + Set the Webflow client ID and secret. The checked-in defaults point the + backend at local Redis and the local Designer Extension. - | Variable | Required | Where to find it | - |---|---|---| - | `WEBFLOW_CLIENT_ID` | Yes | Your app's OAuth Client ID | - | `WEBFLOW_CLIENT_SECRET` | Yes | Your app's OAuth Client Secret | - | `NGROK_AUTH_TOKEN` | No | Only needed for external testing | - - The remaining variables (`PORT`, `DESIGNER_EXTENSION_URI`) have sensible defaults and can be left as-is. - - > **Important:** the `.env` file contains secrets and is git-ignored. The `.env.example` file in the repo serves as a reference for which variables are needed. Never commit actual API keys. - -5. **Start the app:** +5. Start both applications: ```bash npm run dev ``` - This starts both servers concurrently: - - **Data Client (backend):** `http://localhost:3001` - - **Designer Extension (frontend):** `http://localhost:1337` + - Data client: `http://localhost:3001` + - Designer Extension: `http://localhost:1337` -6. **Authorize the app** by navigating to `http://localhost:3001` in your browser — this redirects to the Webflow OAuth consent screen. Click **Authorize**. +6. Open `http://localhost:3001` to authorize the app, then launch the + development app from the Webflow Designer Apps panel. -7. **Open the Designer Extension** in the Webflow Designer: Apps panel → your app → **"Launch Development App"**. - -## Environment Variables +## Environment variables | Variable | Required | Description | |---|---|---| | `WEBFLOW_CLIENT_ID` | Yes | OAuth client ID from Webflow | -| `WEBFLOW_CLIENT_SECRET` | Yes | OAuth client secret from Webflow | -| `NGROK_AUTH_TOKEN` | No | For exposing localhost during development | -| `PORT` | No | Data client port (default: `3001`) | -| `DESIGNER_EXTENSION_URI` | No | Extension origin for CORS + postMessage (default: `http://localhost:1337`) | +| `WEBFLOW_CLIENT_SECRET` | Yes | OAuth client secret; also derives the at-rest encryption key | +| `REDIS_URL` | Yes | Redis connection URL | +| `APP_BASE_URL` | Yes | Public origin of the data client; its OAuth callback must match Webflow exactly | +| `DESIGNER_EXTENSION_URI` | Yes | Exact Designer Extension origin allowed by CORS and `postMessage` | +| `PORT` | No | Local data-client port; defaults to `3001` | +| `NGROK_AUTH_TOKEN` | No | Development tunnel credential | + +Keep `.env` files out of version control; they contain credentials. + +## Script ownership and uninstalling + +Webflow only exposes custom code owned by this app. It cannot detect a Simple +Analytics script added manually in Site settings or by another app. Remove any +existing manual installation before using this app to avoid duplicate pageviews. + +Before uninstalling the Webflow App, choose **Disconnect Webflow** in the +extension and confirm the action. This removes the app-owned site script, +deletes the stored site authorization, and revokes the Webflow grant when its +last connected site is removed. Publish the Webflow site afterward so script +removal takes effect. + +## Validation + +```bash +npm test --prefix data-client +npm run lint --prefix data-client +npm run lint --prefix designer-extension +npm run build +``` ## Security -- **Tokens encrypted at rest** — access tokens are AES-256-GCM encrypted in the SQLite database, keyed from `WEBFLOW_CLIENT_SECRET`. -- **Env validation** — the server refuses to start if required credentials are missing. -- **CORS** — the API only accepts requests from the configured `DESIGNER_EXTENSION_URI`. -- **Security headers** — `X-Content-Type-Options: nosniff` and `X-Frame-Options: DENY` on all API responses. -- **postMessage origin** — the OAuth callback targets the extension origin instead of `*`. +- OAuth tokens are encrypted with AES-256-GCM before Redis persistence. +- Designer sessions are short-lived, site-bound JWTs. +- OAuth callbacks use a one-time state value and the same configured redirect + URI for authorization and token exchange. +- API CORS is restricted to `DESIGNER_EXTENSION_URI`. +- Disconnect removes app-owned custom code and stored authorization data. -## Tech Stack +## Tech stack -- **Data Client:** [Next.js](https://nextjs.org/), [Webflow SDK](https://github.com/webflow/js-webflow-api), SQLite -- **Designer Extension:** [React](https://react.dev/), [Vite](https://vitejs.dev/), [Webflow Designer API](https://developers.webflow.com/designer/reference/introduction) +- Next.js, Redis, and the Webflow Data API +- React, Vite, and the Webflow Designer API ## License diff --git a/data-client/app/api/auth/callback/route.ts b/data-client/app/api/auth/callback/route.ts index 3644379..0e0478f 100644 --- a/data-client/app/api/auth/callback/route.ts +++ b/data-client/app/api/auth/callback/route.ts @@ -2,10 +2,19 @@ import { timingSafeEqual } from "node:crypto"; import { WebflowClient } from "webflow-api"; import { NextRequest, NextResponse } from "next/server"; import db, { getRedis } from "../../../lib/utils/database"; -import { extensionOrigin, requiredEnv } from "../../../lib/utils/config"; +import { appOrigin, extensionOrigin, requiredEnv } from "../../../lib/utils/config"; export const dynamic = "force-dynamic"; +export function exchangeAuthorizationCode(code: string) { + return WebflowClient.getAccessToken({ + clientId: requiredEnv("WEBFLOW_CLIENT_ID"), + clientSecret: requiredEnv("WEBFLOW_CLIENT_SECRET"), + redirectUri: `${appOrigin()}/api/auth/callback`, + code, + }); +} + export async function GET(request: NextRequest) { const state = request.nextUrl.searchParams.get("state"); const cookie = request.cookies.get("webflow_oauth_state")?.value; @@ -19,10 +28,7 @@ export async function GET(request: NextRequest) { const saved = await (await getRedis()).getDel(`oauth:${state}`); if (!saved) return NextResponse.json({ error: "Authorization expired. Please connect again." }, { status: 400 }); const { popup } = JSON.parse(saved); - const accessToken = await WebflowClient.getAccessToken({ - clientId: requiredEnv("WEBFLOW_CLIENT_ID"), - clientSecret: requiredEnv("WEBFLOW_CLIENT_SECRET"), code, - }); + const accessToken = await exchangeAuthorizationCode(code); const webflow = new WebflowClient({ accessToken }); const { sites = [] } = await webflow.sites.list(); await Promise.all(sites.map(site => db.insertSiteAuthorization(site.id, accessToken))); diff --git a/data-client/app/api/auth/disconnect/route.ts b/data-client/app/api/auth/disconnect/route.ts new file mode 100644 index 0000000..b6210c4 --- /dev/null +++ b/data-client/app/api/auth/disconnect/route.ts @@ -0,0 +1,73 @@ +import { NextRequest, NextResponse } from "next/server"; +import { WebflowClient } from "webflow-api"; +import jwt from "../../../lib/utils/jwt"; +import db from "../../../lib/utils/database"; +import { requiredEnv } from "../../../lib/utils/config"; +import { removeSiteScript } from "../../../lib/controllers/scriptController"; + +export const dynamic = "force-dynamic"; + +export async function revokeAuthorization(accessToken: string): Promise { + const response = await fetch("https://webflow.com/oauth/revoke_authorization", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + client_id: requiredEnv("WEBFLOW_CLIENT_ID"), + client_secret: requiredEnv("WEBFLOW_CLIENT_SECRET"), + access_token: accessToken, + }), + signal: AbortSignal.timeout(10_000), + }); + if (!response.ok) throw new Error("Webflow authorization revocation failed"); +} + +/** + * POST /api/auth/disconnect + * + * Removes the app's custom code and stored authorization for one site. A + * shared Webflow grant is revoked only when no other site uses it. + */ +export async function POST(request: NextRequest) { + let body: unknown; + try { + body = await request.json(); + } catch { + return NextResponse.json({ error: "Invalid request" }, { status: 400 }); + } + + const siteId = (body as { siteId?: unknown } | null)?.siteId; + if (typeof siteId !== "string" || !siteId) { + return NextResponse.json({ error: "siteId is required" }, { status: 400 }); + } + + const accessToken = await jwt.verifyAuth(request, siteId); + if (!accessToken) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + try { + const webflow = new WebflowClient({ accessToken }); + await removeSiteScript(webflow, siteId); + const remainingSites = await db.removeSiteAuthorization(siteId, accessToken); + + if (remainingSites === 0) { + try { + await revokeAuthorization(accessToken); + } catch { + // Keep the local authorization usable so the user can retry cleanup. + await db.insertSiteAuthorization(siteId, accessToken); + return NextResponse.json( + { error: "Webflow could not be disconnected. Please try again." }, + { status: 502 } + ); + } + } + + return NextResponse.json({ success: true }); + } catch { + return NextResponse.json( + { error: "Failed to disconnect Webflow" }, + { status: 500 } + ); + } +} diff --git a/data-client/app/lib/controllers/scriptController.ts b/data-client/app/lib/controllers/scriptController.ts index e51b555..f1051ed 100644 --- a/data-client/app/lib/controllers/scriptController.ts +++ b/data-client/app/lib/controllers/scriptController.ts @@ -1,4 +1,4 @@ -import { WebflowClient } from "webflow-api"; +import { Webflow, WebflowClient } from "webflow-api"; const SCRIPT_DISPLAY_NAME = "Simple Analytics"; @@ -82,18 +82,16 @@ export async function applySiteScript( interface ScriptStatus { installed: boolean; version?: string; - duplicateDetected: boolean; } /** - * Check if a Simple Analytics script is already registered for a site, - * and detect duplicate SA scripts (e.g. manually added via Project Settings). + * Check if a Simple Analytics script registered by this app is applied to a site. + * Webflow does not expose custom code owned by other apps or added manually. */ export async function checkScriptStatus( webflow: WebflowClient, siteId: string ): Promise { - // Check registered scripts from our app const existingScripts = await webflow.scripts.list(siteId); const matching = (existingScripts.registeredScripts || []) .filter((s) => s.displayName === SCRIPT_DISPLAY_NAME) @@ -109,36 +107,7 @@ export async function checkScriptStatus( const installed = Boolean(installedScript); const version = installedScript?.version; - // Check for duplicate SA scripts on the site (e.g. manually added) - let duplicateDetected = false; - try { - const scripts = applied; - - // Count how many applied scripts reference simpleanalyticscdn in their source - // Our app registers scripts with displayName "Simple Analytics", - // so any OTHER script pointing to simpleanalyticscdn.com is a duplicate - const ourScriptIds = new Set(matching.map((s) => s.id)); - const otherSAScripts = scripts.filter((s: { id?: string }) => { - // If this script is one of ours, skip it - if (s.id && ourScriptIds.has(s.id)) return false; - - // Check registered scripts list for source URL - const registered = (existingScripts.registeredScripts || []).find( - (r) => r.id === s.id - ); - const reg = registered as Record | undefined; - if (typeof reg?.sourceUrl === "string" && reg.sourceUrl.includes("simpleanalyticscdn.com")) return true; - if (typeof reg?.source === "string" && reg.source.includes("simpleanalyticscdn.com")) return true; - - return false; - }); - - duplicateDetected = otherSAScripts.length > 0; - } catch { - // getCustomCode may fail if no custom code exists yet — that's fine - } - - return { installed, version, duplicateDetected }; + return { installed, version }; } /** @@ -148,5 +117,10 @@ export async function removeSiteScript( webflow: WebflowClient, siteId: string ): Promise { - await webflow.sites.scripts.deleteCustomCode(siteId); + try { + await webflow.sites.scripts.deleteCustomCode(siteId); + } catch (error) { + // Disconnect is idempotent when the app has no custom code on the site. + if (!(error instanceof Webflow.NotFoundError)) throw error; + } } diff --git a/data-client/app/lib/utils/database.ts b/data-client/app/lib/utils/database.ts index cb6cf40..63cdd02 100644 --- a/data-client/app/lib/utils/database.ts +++ b/data-client/app/lib/utils/database.ts @@ -1,5 +1,11 @@ import { createClient } from "redis"; -import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from "node:crypto"; +import { + createCipheriv, + createDecipheriv, + createHash, + randomBytes, + scryptSync, +} from "node:crypto"; import { requiredEnv } from "./config"; // Only ciphertext is persisted, including in Redis AOF/snapshots. @@ -22,6 +28,11 @@ export function decrypt(encoded: string): string { let client: ReturnType | undefined; let connecting: Promise | undefined; +function authorizationSitesKey(accessToken: string): string { + const fingerprint = createHash("sha256").update(accessToken).digest("hex"); + return `authorization:${fingerprint}:sites`; +} + export async function getRedis() { if (!client) { client = createClient({ @@ -41,7 +52,83 @@ export async function getRedis() { export async function insertSiteAuthorization(siteId: string, accessToken: string) { const db = await getRedis(); - await db.set(`site:${siteId}`, encrypt(accessToken)); + const siteKey = `site:${siteId}`; + const previous = await db.get(siteKey); + let previousSitesKey: string | undefined; + if (previous) { + try { + const previousToken = decrypt(previous); + if (previousToken !== accessToken) { + previousSitesKey = authorizationSitesKey(previousToken); + } + } catch { + // Replacing an unreadable entry repairs the site mapping. + } + } + const sitesKey = authorizationSitesKey(accessToken); + await db.eval( + `redis.call("SET", KEYS[1], ARGV[1]) + redis.call("SADD", KEYS[2], ARGV[2]) + if KEYS[3] then + redis.call("SREM", KEYS[3], ARGV[2]) + if redis.call("SCARD", KEYS[3]) == 0 then redis.call("DEL", KEYS[3]) end + end + return 1`, + { + keys: previousSitesKey + ? [siteKey, sitesKey, previousSitesKey] + : [siteKey, sitesKey], + arguments: [encrypt(accessToken), siteId], + } + ); +} + +export async function removeSiteAuthorization( + siteId: string, + accessToken: string +): Promise { + const db = await getRedis(); + const key = `site:${siteId}`; + const ciphertext = await db.get(key); + if (!ciphertext) throw new Error("Site authorization changed"); + + if (decrypt(ciphertext) !== accessToken) throw new Error("Site authorization changed"); + + // One Webflow grant can authorize multiple sites. Only revoke it upstream + // after its final site mapping has been removed. + const sitesKey = authorizationSitesKey(accessToken); + const result = await db.eval( + `if redis.call("GET", KEYS[1]) ~= ARGV[1] then return -1 end + redis.call("DEL", KEYS[1]) + redis.call("SREM", KEYS[2], ARGV[2]) + return redis.call("SCARD", KEYS[2])`, + { keys: [key, sitesKey], arguments: [ciphertext, siteId] } + ); + if (result === -1) throw new Error("Site authorization changed"); + let remainingSites = Number(result); + + if (remainingSites === 0) { + // Backfill the index for mappings written before disconnect support. + for await (const keys of db.scanIterator({ MATCH: "site:*", COUNT: 100 })) { + if (keys.length === 0) continue; + const values = await db.mGet(keys); + for (let index = 0; index < values.length; index += 1) { + const value = values[index]; + if (!value) continue; + try { + if (decrypt(value) === accessToken) { + await db.sAdd(sitesKey, keys[index].slice("site:".length)); + remainingSites += 1; + } + } catch { + // Ignore unrelated corrupt entries; they cannot contain this token. + } + } + } + } + + if (remainingSites === 0) await db.del(sitesKey); + return remainingSites; } export async function getAccessTokenFromSiteId(siteId: string): Promise { @@ -51,4 +138,8 @@ export async function getAccessTokenFromSiteId(siteId: string): Promise return decrypt(ciphertext); } -export default { insertSiteAuthorization, getAccessTokenFromSiteId }; +export default { + insertSiteAuthorization, + removeSiteAuthorization, + getAccessTokenFromSiteId, +}; diff --git a/data-client/tests/security.test.ts b/data-client/tests/security.test.ts index 17658bd..03d2902 100644 --- a/data-client/tests/security.test.ts +++ b/data-client/tests/security.test.ts @@ -3,15 +3,18 @@ import assert from "node:assert/strict"; import { runInNewContext } from "node:vm"; import { NextRequest } from "next/server"; import { SignJWT } from "jose"; +import { WebflowClient } from "webflow-api"; import db, { encrypt, decrypt } from "../app/lib/utils/database"; import jwt from "../app/lib/utils/jwt"; import { POST as exchange } from "../app/api/auth/token/route"; -import { GET as callback } from "../app/api/auth/callback/route"; +import { GET as callback, exchangeAuthorizationCode } from "../app/api/auth/callback/route"; +import { revokeAuthorization } from "../app/api/auth/disconnect/route"; import { generateScript, type ScriptConfig } from "../app/lib/utils/scriptGenerator"; import { checkScriptStatus } from "../app/lib/controllers/scriptController"; -import type { WebflowClient } from "webflow-api"; process.env.WEBFLOW_CLIENT_SECRET = "local-test-secret-not-used-in-production"; +process.env.WEBFLOW_CLIENT_ID = "local-test-client"; +process.env.APP_BASE_URL = "https://webflow-app.example"; afterEach(() => mock.restoreAll()); test("tokens are encrypted with random IVs and reject tampering", () => { @@ -67,6 +70,30 @@ test("OAuth rejects callbacks without a matching browser nonce before token exch assert.equal(fetchMock.mock.callCount(), 0); }); +test("OAuth token exchange repeats the exact configured callback URI", async () => { + const getAccessToken = mock.method(WebflowClient, "getAccessToken", async () => "access-token"); + assert.equal(await exchangeAuthorizationCode("authorization-code"), "access-token"); + assert.deepEqual(getAccessToken.mock.calls[0].arguments[0], { + clientId: "local-test-client", + clientSecret: "local-test-secret-not-used-in-production", + redirectUri: "https://webflow-app.example/api/auth/callback", + code: "authorization-code", + }); +}); + +test("Webflow revocation sends the grant only to the official endpoint", async () => { + const fetchMock = mock.method(globalThis, "fetch", async () => new Response(null, { status: 200 })); + await revokeAuthorization("access-token"); + assert.equal(fetchMock.mock.calls[0].arguments[0], "https://webflow.com/oauth/revoke_authorization"); + const options = fetchMock.mock.calls[0].arguments[1] as RequestInit; + assert.equal(options.method, "POST"); + assert.deepEqual(JSON.parse(options.body as string), { + client_id: "local-test-client", + client_secret: "local-test-secret-not-used-in-production", + access_token: "access-token", + }); +}); + const config: ScriptConfig = { trackPageviews: true, trackEvents: false, collectDNT: false, automatedEvents: true, hashMode: false, ignorePages: "", customDomain: "", overwriteDomain: "", @@ -89,5 +116,5 @@ test("script settings are serialized safely and disabled event types stay disabl test("a registered script is not installed after it has been removed", async () => { const webflow = { scripts: { list: async () => ({ registeredScripts: [{ id: "sa", displayName: "Simple Analytics", version: "1.0.0" }] }) }, sites: { scripts: { getCustomCode: async () => ({ scripts: [] }) } } } as unknown as WebflowClient; - assert.equal((await checkScriptStatus(webflow, "site-a")).installed, false); + assert.deepEqual(await checkScriptStatus(webflow, "site-a"), { installed: false, version: undefined }); }); diff --git a/designer-extension/src/App.css b/designer-extension/src/App.css index c249bac..4332fcc 100644 --- a/designer-extension/src/App.css +++ b/designer-extension/src/App.css @@ -440,3 +440,8 @@ body { .footer-disconnect:hover { color: #ccc; } + +.footer-disconnect:disabled { + color: #555; + cursor: not-allowed; +} diff --git a/designer-extension/src/App.tsx b/designer-extension/src/App.tsx index f231811..c4a706b 100644 --- a/designer-extension/src/App.tsx +++ b/designer-extension/src/App.tsx @@ -5,7 +5,7 @@ import { ConfigPanel } from "./components/ConfigPanel"; import "./App.css"; function App() { - const { sessionToken, isAuthLoading, exchangeAndVerifyIdToken, logout } = useAuth(); + const { sessionToken, isAuthLoading, exchangeAndVerifyIdToken, clearSession } = useAuth(); useEffect(() => { webflow.setExtensionSize("comfortable"); @@ -27,7 +27,7 @@ function App() { } if (sessionToken) { - return ; + return ; } return ; diff --git a/designer-extension/src/components/ConfigPanel.tsx b/designer-extension/src/components/ConfigPanel.tsx index 31b8466..c018952 100644 --- a/designer-extension/src/components/ConfigPanel.tsx +++ b/designer-extension/src/components/ConfigPanel.tsx @@ -2,7 +2,8 @@ import { useState, useCallback, useEffect } from "react"; import { useSites } from "../hooks/useSites"; import { useInstallScript } from "../hooks/useInstallScript"; import { StatusMessage } from "./StatusMessage"; -import { ScriptConfig } from "../types/types"; +import { ScriptConfig, StatusInfo } from "../types/types"; +import { disconnectWebflow } from "../services/api"; const DEFAULT_CONFIG: ScriptConfig = { trackPageviews: true, @@ -50,10 +51,10 @@ function saveConfig(siteId: string | null, config: ScriptConfig) { interface ConfigPanelProps { sessionToken: string; - onLogout: () => void; + onDisconnect: () => void; } -export function ConfigPanel({ sessionToken, onLogout }: ConfigPanelProps) { +export function ConfigPanel({ sessionToken, onDisconnect }: ConfigPanelProps) { const { siteId } = useSites(); const { install, @@ -67,20 +68,27 @@ export function ConfigPanel({ sessionToken, onLogout }: ConfigPanelProps) { const [activeTab, setActiveTab] = useState<"settings" | "info">("settings"); const [showAdvanced, setShowAdvanced] = useState(false); const [showEvents, setShowEvents] = useState(false); + const [isDisconnecting, setIsDisconnecting] = useState(false); + const [disconnectStatus, setDisconnectStatus] = useState({ + message: "", + type: "", + }); const [config, setConfig] = useState(() => loadConfig(siteId)); + const [loadedSiteId, setLoadedSiteId] = useState(null); // Reload config when siteId becomes available useEffect(() => { if (siteId) { setConfig(loadConfig(siteId)); + setLoadedSiteId(siteId); } }, [siteId]); - // Persist config on every change + // Do not persist defaults until the saved config for this site has loaded. useEffect(() => { - saveConfig(siteId, config); - }, [siteId, config]); + if (siteId && loadedSiteId === siteId) saveConfig(siteId, config); + }, [siteId, loadedSiteId, config]); function handleToggle(key: keyof ScriptConfig) { setConfig((prev) => ({ ...prev, [key]: !prev[key] })); @@ -111,9 +119,43 @@ export function ConfigPanel({ sessionToken, onLogout }: ConfigPanelProps) { }, []); function handleInstall() { + setDisconnectStatus({ message: "", type: "" }); install(config); } + async function handleDisconnect() { + if (!siteId) { + setDisconnectStatus({ + message: "Could not detect site. Open this app from the Webflow Designer.", + type: "error", + }); + return; + } + if (!window.confirm( + "Disconnect Simple Analytics from this site? This removes the app's script and stored Webflow authorization. Publish the site afterward to apply the script removal." + )) return; + + setIsDisconnecting(true); + setDisconnectStatus({ message: "Disconnecting Webflow...", type: "info" }); + try { + await disconnectWebflow(siteId, sessionToken); + const key = getStorageKey(siteId); + try { + if (key) localStorage.removeItem(key); + } catch { + // Authorization is already removed; local storage may be unavailable. + } + onDisconnect(); + } catch (error) { + setDisconnectStatus({ + message: `Error: ${error instanceof Error ? error.message : "Disconnection failed"}`, + type: "error", + }); + } finally { + setIsDisconnecting(false); + } + } + return (
{/* Tab bar */} @@ -413,30 +455,35 @@ export function ConfigPanel({ sessionToken, onLogout }: ConfigPanelProps) { ) : ( - + <> +
+ Already added Simple Analytics in Webflow Site settings? Remove + that manual script before installing to avoid duplicate pageviews. +
+ + )}
- )} @@ -460,6 +507,11 @@ export function ConfigPanel({ sessionToken, onLogout }: ConfigPanelProps) { script to your site. After installing, publish your site to start collecting data.

+

+ If Simple Analytics was previously added in Webflow Site settings, + remove that manual script first. Webflow does not let apps inspect + custom code added manually or by other apps. +

@@ -483,6 +535,8 @@ export function ConfigPanel({ sessionToken, onLogout }: ConfigPanelProps) {
)} + +
Powered by{" "} @@ -495,8 +549,12 @@ export function ConfigPanel({ sessionToken, onLogout }: ConfigPanelProps) { - · diff --git a/designer-extension/src/hooks/useAuth.ts b/designer-extension/src/hooks/useAuth.ts index 8b3d26b..d6f8070 100644 --- a/designer-extension/src/hooks/useAuth.ts +++ b/designer-extension/src/hooks/useAuth.ts @@ -50,5 +50,10 @@ export function useAuth() { return () => clearTimeout(timer); }, [sessionToken, exchangeAndVerifyIdToken]); - return { sessionToken, isAuthLoading, exchangeAndVerifyIdToken, logout: () => setSessionToken("") }; + return { + sessionToken, + isAuthLoading, + exchangeAndVerifyIdToken, + clearSession: () => setSessionToken(""), + }; } diff --git a/designer-extension/src/hooks/useInstallScript.ts b/designer-extension/src/hooks/useInstallScript.ts index d4b273f..321646b 100644 --- a/designer-extension/src/hooks/useInstallScript.ts +++ b/designer-extension/src/hooks/useInstallScript.ts @@ -18,7 +18,6 @@ export function useInstallScript( const [isRemoving, setIsRemoving] = useState(false); const [isChecking, setIsChecking] = useState(true); const [isInstalled, setIsInstalled] = useState(false); - const [duplicateDetected, setDuplicateDetected] = useState(false); const [scriptVersion, setScriptVersion] = useState(null); const [status, setStatus] = useState({ message: "", type: "" }); @@ -37,7 +36,6 @@ export function useInstallScript( if (!cancelled) { setIsInstalled(result.installed); setScriptVersion(result.version ?? null); - setDuplicateDetected(result.duplicateDetected); } } catch { // Silent fail — default to not installed @@ -133,7 +131,6 @@ export function useInstallScript( isRemoving, isChecking, isInstalled, - duplicateDetected, scriptVersion, status, }; diff --git a/designer-extension/src/services/api.ts b/designer-extension/src/services/api.ts index cec52ed..aa51ce9 100644 --- a/designer-extension/src/services/api.ts +++ b/designer-extension/src/services/api.ts @@ -94,3 +94,20 @@ export async function removeScript( throw new Error(data.error || "Failed to remove script"); } } + +/** POST /api/auth/disconnect - remove app data and authorization for this site */ +export async function disconnectWebflow( + siteId: string, + token: string +): Promise { + const response = await fetchWithTimeout(`${BASE_URL}/api/auth/disconnect`, { + method: "POST", + headers: authHeaders(token), + body: JSON.stringify({ siteId }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || "Failed to disconnect Webflow"); + } +} diff --git a/designer-extension/src/types/types.ts b/designer-extension/src/types/types.ts index 96d1582..2357484 100644 --- a/designer-extension/src/types/types.ts +++ b/designer-extension/src/types/types.ts @@ -50,7 +50,6 @@ export interface RegisteredScript { export interface ScriptStatus { installed: boolean; version?: string; - duplicateDetected: boolean; } // Status message display diff --git a/scripts/smoke.mjs b/scripts/smoke.mjs index b8a8ac4..cbbc556 100644 --- a/scripts/smoke.mjs +++ b/scripts/smoke.mjs @@ -16,6 +16,8 @@ if (process.argv.includes("--unhealthy")) { const response = await request(`/api/scripts/${action}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ siteId: "test-site", scriptId: "test-script", version: "1.0.0" }) }); assert.equal(response.status, 401); } + const disconnect = await request("/api/auth/disconnect", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ siteId: "test-site" }) }); + assert.equal(disconnect.status, 401); for (const path of ["/api/sites", "/api/scripts/status?siteId=test-site"]) { assert.equal((await request(path)).status, 401); }