diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5e3f262 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# VCS +.git + +# Node +node_modules + +# Build artifacts / Logs +.turbo +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# OS / IDE generated +.DS_Store +*.pem +*.key + +# Examples (ignore all except the one we are building) +examples/* +!examples/bun-stream-server + +# Packages (ignore node_modules within packages, keep source) +packages/*/node_modules +packages/*/dist +packages/*/tsconfig.tsbuildinfo + +# Specific files +.env +.env.* +!.env.example \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9df89b2..99ff696 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v2 with: - version: 8 + version: 9 - name: Get pnpm store directory shell: bash @@ -66,10 +66,10 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v2 with: - version: 8 + version: 9 - name: Install dependencies run: pnpm install - name: Lint - run: pnpm lint \ No newline at end of file + run: pnpm lint diff --git a/.github/workflows/cloudflare-preview-cleanup.yml b/.github/workflows/cloudflare-preview-cleanup.yml new file mode 100644 index 0000000..6867d68 --- /dev/null +++ b/.github/workflows/cloudflare-preview-cleanup.yml @@ -0,0 +1,37 @@ +name: Cloudflare Preview Cleanup + +on: + pull_request: + branches: [ main ] + types: [closed] + +jobs: + cleanup-preview: + runs-on: ubuntu-latest + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} + STREAM_KIT_WORKER_NAME: bun-stream-server-pr-${{ github.event.pull_request.number }} + STREAM_KIT_CONTAINER_NAME: codeflare-containers-pr-${{ github.event.pull_request.number }} + STREAM_KIT_CONFIG_PATH: ${{ github.workspace }}/examples/bun-stream-server/.wrangler/preview-wrangler.json + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.x" + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 9 + + - name: Install dependencies + run: pnpm install + + - name: Render Wrangler config + run: node examples/bun-stream-server/scripts/render-wrangler-config.mjs + + - name: Delete preview Worker and container + run: node examples/bun-stream-server/scripts/cleanup-preview.mjs diff --git a/.github/workflows/cloudflare-preview-e2e.yml b/.github/workflows/cloudflare-preview-e2e.yml new file mode 100644 index 0000000..7fcce8f --- /dev/null +++ b/.github/workflows/cloudflare-preview-e2e.yml @@ -0,0 +1,149 @@ +name: Cloudflare Preview E2E + +on: + pull_request: + branches: [ main ] + types: [opened, synchronize, reopened] + +concurrency: + group: cloudflare-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + deploy-preview: + runs-on: ubuntu-latest + outputs: + preview_url: ${{ steps.deploy.outputs.preview_url }} + worker_name: ${{ steps.names.outputs.worker_name }} + container_name: ${{ steps.names.outputs.container_name }} + container_image: ${{ steps.image.outputs.container_image }} + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} + STREAM_KIT_TURN_API_TOKEN: ${{ secrets.CLOUDFLARE_TURN_API_TOKEN }} + STREAM_KIT_TURN_KEY_ID: ${{ secrets.CLOUDFLARE_TURN_KEY_ID }} + STREAM_KIT_DEBUG_STATE_TOKEN: ${{ secrets.DEBUG_STATE_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.x" + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 9 + + - name: Install dependencies + run: pnpm install + + - name: Validate required Cloudflare secrets + run: | + test -n "$CLOUDFLARE_API_TOKEN" || (echo "CLOUDFLARE_API_TOKEN is required" && exit 1) + test -n "$CLOUDFLARE_ACCOUNT_ID" || (echo "CLOUDFLARE_ACCOUNT_ID is required" && exit 1) + test -n "$STREAM_KIT_TURN_API_TOKEN" || (echo "CLOUDFLARE_TURN_API_TOKEN is required" && exit 1) + test -n "$STREAM_KIT_TURN_KEY_ID" || (echo "CLOUDFLARE_TURN_KEY_ID is required" && exit 1) + + - name: Compute preview names + id: names + run: | + echo "worker_name=bun-stream-server-pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + echo "container_name=codeflare-containers-pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + + - name: Resolve base container image + id: image + working-directory: examples/bun-stream-server + run: | + container_image=$(node <<'NODE' + const { execFileSync } = require("node:child_process"); + const output = execFileSync("npx", ["wrangler", "containers", "list"], { + encoding: "utf8", + cwd: process.cwd(), + }); + const match = output.match(/(\[\s*\{[\s\S]*\])\s*$/); + if (!match) { + throw new Error(`Could not parse wrangler containers list output:\n${output}`); + } + const containers = JSON.parse(match[1]); + const container = containers.find((item) => item.name === "codeflare-containers"); + if (!container || !container.configuration?.image) { + throw new Error("Could not resolve the base codeflare-containers image"); + } + process.stdout.write(container.configuration.image); + NODE + ) + echo "container_image=$container_image" >> "$GITHUB_OUTPUT" + + - name: Render Wrangler config + env: + STREAM_KIT_WORKER_NAME: ${{ steps.names.outputs.worker_name }} + STREAM_KIT_CONTAINER_NAME: ${{ steps.names.outputs.container_name }} + STREAM_KIT_CONTAINER_IMAGE: ${{ steps.image.outputs.container_image }} + STREAM_KIT_CONFIG_PATH: ${{ github.workspace }}/examples/bun-stream-server/.wrangler/preview-wrangler.json + run: node examples/bun-stream-server/scripts/render-wrangler-config.mjs + + - name: Clean any stale preview resources + env: + STREAM_KIT_WORKER_NAME: ${{ steps.names.outputs.worker_name }} + STREAM_KIT_CONTAINER_NAME: ${{ steps.names.outputs.container_name }} + STREAM_KIT_CONFIG_PATH: ${{ github.workspace }}/examples/bun-stream-server/.wrangler/preview-wrangler.json + run: node examples/bun-stream-server/scripts/cleanup-preview.mjs || true + + - name: Deploy preview Worker + id: deploy + working-directory: examples/bun-stream-server + run: | + set -euo pipefail + deploy_output=$(npx wrangler deploy --config .wrangler/preview-wrangler.json --name "${{ steps.names.outputs.worker_name }}" 2>&1 | tee /tmp/wrangler-deploy.log) + preview_url=$(printf '%s\n' "$deploy_output" | grep -Eo 'https://[A-Za-z0-9._/-]+workers.dev' | tail -n1) + if [ -z "$preview_url" ]; then + echo "Failed to parse preview URL from wrangler deploy output" + exit 1 + fi + echo "preview_url=$preview_url" >> "$GITHUB_OUTPUT" + + - name: Configure preview Worker secrets + working-directory: examples/bun-stream-server + env: + WORKER_NAME: ${{ steps.names.outputs.worker_name }} + run: | + printf '%s' "$STREAM_KIT_TURN_API_TOKEN" | npx wrangler secret put CLOUDFLARE_TURN_API_TOKEN --config .wrangler/preview-wrangler.json --name "$WORKER_NAME" + printf '%s' "$STREAM_KIT_TURN_KEY_ID" | npx wrangler secret put CLOUDFLARE_TURN_KEY_ID --config .wrangler/preview-wrangler.json --name "$WORKER_NAME" + if [ -n "${STREAM_KIT_DEBUG_STATE_TOKEN:-}" ]; then + printf '%s' "$STREAM_KIT_DEBUG_STATE_TOKEN" | npx wrangler secret put DEBUG_STATE_TOKEN --config .wrangler/preview-wrangler.json --name "$WORKER_NAME" + fi + + e2e: + runs-on: ubuntu-latest + needs: deploy-preview + env: + E2E_STREAM_SERVER_URL: ${{ needs.deploy-preview.outputs.preview_url }} + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.x" + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 9 + + - name: Install dependencies + run: pnpm install + + - name: Run deployed stream E2E + run: pnpm --dir examples/bun-stream-server test:e2e + + - name: Upload Playwright artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-preview-artifacts + path: | + examples/bun-stream-server/playwright-report + examples/bun-stream-server/test-results diff --git a/.gitignore b/.gitignore index 39dd837..9f04b65 100644 --- a/.gitignore +++ b/.gitignore @@ -52,11 +52,14 @@ junit.xml # Logs *.log -# Docker -Dockerfile -.dockerignore -docker-compose.yml -*.dockerfile - # Wrangler -.wrangler/ \ No newline at end of file +.wrangler/ +playwright-report/ +test-results/ + +# LLM context docs +.llms + +# Agent working files +.swarm/ +.claude/ diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..681311e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..0d9658f --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,10 @@ +# Stream Kit Architecture + +The canonical architecture document now lives at [`docs/architecture.md`](./docs/architecture.md). + +For the latest repo map and current status, start with: + +- [`docs/index.md`](./docs/index.md) +- [`docs/architecture.md`](./docs/architecture.md) +- [`docs/integration.md`](./docs/integration.md) +- [`docs/status.md`](./docs/status.md) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a4dd059 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,61 @@ +# AGENTS.md + +## Project + +- **Name:** Stream Kit +- **Description:** OGS cloud rendering and remote streaming workspace +- **Tech stack:** TypeScript, pnpm workspaces, Turbo, Vitest, Cloudflare Workers, Durable Objects, Containers, PeerJS, Puppeteer +- **Package manager:** pnpm +- **Source layout:** + - `packages/` — reusable SDK and testing packages + - `examples/bun-stream-server/` — working Cloudflare runtime example + - `docs/` — repo map, integration guidance, and current status + +## Feedback Commands + +Run in this order before committing: + +1. `pnpm test` +2. `pnpm typecheck` +3. `pnpm build` + +## Knowledge Base + +Start here. Load deeper docs only when needed. + +| Topic | Location | +|---|---| +| Docs catalog | [docs/index.md](docs/index.md) | +| Architecture | [docs/architecture.md](docs/architecture.md) | +| OGS integration and target SDK shape | [docs/integration.md](docs/integration.md) | +| Current verified state and next steps | [docs/status.md](docs/status.md) | +| Cloudflare example walkthrough | [examples/bun-stream-server/README.md](examples/bun-stream-server/README.md) | +| Workspace overview | [README.md](README.md) | + +> Progressive disclosure: do not load all docs up front. Start with this file, then open only the doc that matches the task. + +## Current Reality + +- The deployed Cloudflare example is working end to end. +- The package layer is still less mature than the example runtime. +- The intended product direction is OGS-owned infrastructure with a simpler client-facing `stream-kit` SDK. +- `app-bridge` should likely be hidden inside the eventual OGS SDK experience rather than exposed as the primary developer API. + +## Boundaries + +- `stream-kit` should own streaming runtime and SDK behavior. +- `opengame-api` should own the public control plane. +- `opengame-app` should own native cast and app-shell UX. + +## Key Conventions + +- Prefer `rg` for search. +- Use `apply_patch` for manual code edits. +- Never revert user changes unless explicitly asked. +- Treat `examples/bun-stream-server/` as the source of truth for end-to-end behavior. +- Keep docs aligned with verified behavior; if the architecture changes, update `docs/`. + +## Git + +- Current branch work should go through a `codex/*` branch for PR prep. +- Keep commits focused and descriptive. diff --git a/README.md b/README.md index 3f88940..3269480 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,63 @@ -# ☁️ @open-game-system/stream-kit +# @open-game-system/stream-kit -Monorepo for the Open Game System (OGS) Cloud Rendering service ("Stream Kit"). +Stream Kit is the Open Game System's cloud rendering and remote streaming workspace. -[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) +Today this repo contains two layers: -## Overview +- the reusable SDK packages under [`packages/`](./packages) +- the working Cloudflare streaming runtime under [`examples/bun-stream-server/`](./examples/bun-stream-server) -Stream Kit enables web games to offload intensive graphics rendering to powerful cloud servers and stream the output via WebRTC to the client. This allows for high-fidelity experiences even on devices with limited GPU capabilities. +The recent Cloudflare work is now verified end to end: the deployed example successfully boots a container, launches Chromium, captures a page, and delivers playable video to the receiver over WebRTC using Cloudflare TURN. -This monorepo contains the core libraries, React bindings, and server implementation for the Stream Kit service. +## Read This First -## Packages +- Repo map: [`docs/index.md`](./docs/index.md) +- Architecture: [`docs/architecture.md`](./docs/architecture.md) +- OGS integration guide: [`docs/integration.md`](./docs/integration.md) +- Current status and next steps: [`docs/status.md`](./docs/status.md) -- [`@open-game-system/stream-kit-types`](packages/stream-kit-types): Core TypeScript types. -- [`@open-game-system/stream-kit-web`](packages/stream-kit-web): Core client library for web browsers. -- [`@open-game-system/stream-kit-react`](packages/stream-kit-react): React hooks and components. -- [`@open-game-system/stream-kit-server`](packages/stream-kit-server): Server-side implementation (headless browser management, signaling). +## What This Repo Is For -## Development +The long-term product direction is: + +- third-party web games integrate a small `stream-kit` SDK +- `stream-kit` hides the `app-bridge` details when running inside `opengame-app` +- `opengame-api` exposes the product-facing control plane +- OGS-owned Cloudflare Workers/Containers run the streaming infrastructure + +That means third-party developers should not need to deploy their own Workers, TURN servers, or container stacks just to use cloud rendering in the OGS ecosystem. + +## What Works Today -This project uses `pnpm` workspaces and `turbo` for managing the monorepo. +- Local streaming works through the Bun/container example. +- Deployed streaming works through the Cloudflare example. +- Cloudflare TURN-backed WebRTC connectivity is working in the deployed path. +- Session routing is isolated per receiver session. +- Worker seam tests cover TURN normalization, debug auth, and session routing. + +## Workspace Layout + +- [`packages/stream-kit-types`](./packages/stream-kit-types): shared TypeScript types +- [`packages/stream-kit-web`](./packages/stream-kit-web): browser-side `RenderStream` client primitives +- [`packages/stream-kit-react`](./packages/stream-kit-react): React bindings around an existing stream +- [`packages/stream-kit-server`](./packages/stream-kit-server): experimental server-side abstractions +- [`packages/stream-kit-testing`](./packages/stream-kit-testing): testing helpers +- [`examples/bun-stream-server`](./examples/bun-stream-server): working Cloudflare Worker/DO/container example + +## Development -1. **Install Dependencies:** - ```bash - pnpm install - ``` -2. **Build all packages:** - ```bash - pnpm build - ``` -3. **Run tests:** - ```bash - pnpm test - ``` +```bash +pnpm install +pnpm build +pnpm test +pnpm typecheck +``` -## Contributing +## Notes -Contributions are welcome! Please see `CONTRIBUTING.md` (to be created). +- The reusable package API is still behind the working Cloudflare example in product maturity. +- The current OGS-facing SDK shape described in `docs/integration.md` is a target design, not a fully published package contract yet. ## License -MIT License. \ No newline at end of file +MIT diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..f0555d6 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,92 @@ +# Stream Kit Architecture + +## Repo Shape + +This repository currently contains two related but distinct layers: + +1. `packages/` — reusable SDK and testing primitives +2. `examples/bun-stream-server/` — the working Cloudflare runtime + +The example is the truth for deployed behavior today. The package layer is the productization path. + +## Current Working Runtime + +The deployed runtime lives in [`examples/bun-stream-server/`](../examples/bun-stream-server). + +Main pieces: + +- [`src/index.ts`](../examples/bun-stream-server/src/index.ts): Worker entrypoint, TURN minting, session routing, DO proxying +- [`container/src/server.ts`](../examples/bun-stream-server/container/src/server.ts): browser lifecycle and container HTTP server +- [`container/extension/`](../examples/bun-stream-server/container/extension): tab capture and PeerJS sender +- [`receiver.html`](../examples/bun-stream-server/receiver.html): browser receiver used for local and deployed testing + +## Deployed Flow + +```mermaid +flowchart LR + Game["Receiver / OGS client"] -->|"GET /ice-servers"| Worker["Cloudflare Worker"] + Game -->|"POST /start-stream"| Worker + Worker --> DO["Durable Object"] + DO --> Container["Cloudflare Container"] + Container --> Chromium["Chromium + extension"] + Chromium -->|"WebRTC media"| Game +``` + +Detailed flow: + +1. The receiver creates a session-scoped ID and PeerJS receiver peer. +2. The receiver asks the Worker for Cloudflare TURN credentials. +3. The receiver posts `/start-stream` with the target URL, receiver peer ID, and session header. +4. The Worker routes that request to a session-scoped Durable Object. +5. The Durable Object starts or reuses a container for that session. +6. The container launches Chromium, loads the target page, and captures the tab through the extension. +7. The extension creates a sender peer and calls the receiver using the same ICE configuration. +8. The receiver attaches the returned media to the page and plays the stream. + +## OGS Product Direction + +The intended product architecture is different from the standalone example: + +- `opengame-app` owns native app UX and cast picker integration. +- `app-bridge` is the transport between the WebView game and the native app. +- `stream-kit` should expose the game-developer-facing SDK and hide most `app-bridge` details. +- `opengame-api` should expose public stream and cast session APIs. +- OGS-owned Cloudflare infrastructure should run the actual streaming runtime. + +## Boundary Recommendation + +### `stream-kit` + +Should own: + +- client SDK primitives +- stream session client behavior +- React components/hooks +- app-bridge integration adapters +- streaming runtime implementation + +Should not be the public place where third-party developers manage infrastructure. + +### `opengame-api` + +Should own: + +- authenticated public API +- developer/game authorization +- entitlement and quota checks +- stream or cast session lifecycle endpoints +- usage accounting and audit trails + +### `opengame-app` + +Should own: + +- native cast UI +- session UX +- native capability bridging + +## Near-Term Reality + +- The deployed example works. +- The package API is not yet the polished OGS SDK shape. +- The internal architecture is now good enough to power a productized API, but that public API still needs to be designed and implemented. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..ff6effd --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +# Stream Kit Docs + +Start here, then load only the document you need. + +| Topic | Location | +|---|---| +| Architecture and repo boundaries | [architecture.md](./architecture.md) | +| OGS integration model and third-party developer story | [integration.md](./integration.md) | +| Current verified state and next steps | [status.md](./status.md) | +| Phase 2 execution plan | [next-steps.md](./next-steps.md) | +| Cloudflare example walkthrough | [../examples/bun-stream-server/README.md](../examples/bun-stream-server/README.md) | + +## Quick Summary + +- The Cloudflare example is the current source of truth for end-to-end streaming. +- The long-term product should be OGS-owned infrastructure, not self-hosting by third-party game developers. +- `stream-kit` should become the simple client SDK layer, likely hiding `app-bridge` internally when running inside `opengame-app`. diff --git a/docs/integration.md b/docs/integration.md new file mode 100644 index 0000000..749e4a8 --- /dev/null +++ b/docs/integration.md @@ -0,0 +1,107 @@ +# OGS Integration Guide + +## Intended Developer Experience + +For a third-party web game inside the OGS app, the developer should ideally: + +1. install `@open-game-system/stream-kit` or `@open-game-system/stream-kit-react` +2. wrap the app in an OGS provider +3. add a `CastButton`, `StreamCanvas`, or similar primitive +4. let OGS handle native bridge wiring, backend orchestration, and Cloudflare runtime management + +The developer should not need to run their own Workers, TURN servers, or Cloudflare containers. + +## Dependency Model + +Recommended model: + +- `stream-kit` depends on `app-bridge` internally +- `stream-kit` exposes the high-level developer API +- `app-bridge` remains mostly an implementation detail + +That keeps the public mental model simple while still using the existing `opengame-app` bridge architecture. + +## Theoretical Getting Started + +### React + +```tsx +import { + OGSProvider, + CastButton, + StreamCanvas, +} from "@open-game-system/stream-kit-react"; + +export function App() { + return ( + + + + + ); +} +``` + +### Non-React + +```ts +import { createOGSClient } from "@open-game-system/stream-kit"; + +const ogs = createOGSClient({ + gameId: "your-game-id", + environment: "production", +}); + +await ogs.cast.showPicker(); + +const stream = await ogs.streaming.createStream({ + route: "/render/world", +}); + +stream.mount(document.getElementById("stream-container")!); +``` + +## How This Should Work Under The Hood + +1. `stream-kit` detects whether it is running inside `opengame-app`. +2. If available, it uses `app-bridge` to talk to the native shell. +3. For cast actions, it dispatches native bridge events rather than exposing raw bridge APIs to the game developer. +4. For cloud rendering, it calls OGS-owned APIs that provision stream sessions on OGS-owned Cloudflare infrastructure. + +## Recommended Public API Shape + +React surface: + +- `OGSProvider` +- `CastButton` +- `StreamCanvas` +- `useCastSession()` +- `useStreamingSupport()` +- `useTVStream()` + +Non-React surface: + +- `createOGSClient()` +- `ogs.cast.showPicker()` +- `ogs.cast.subscribe()` +- `ogs.streaming.createStream()` +- `ogs.streaming.startTVStream()` + +## What OGS Should Own + +- `opengame-app`: native cast/session UX +- `opengame-api`: public control plane endpoints +- OGS Cloudflare Workers/Containers: streaming runtime + +## What Is Already True In The Codebase + +- `opengame-app` already exposes native cast state and `SHOW_CAST_PICKER` through `app-bridge`. +- `stream-kit` already has a working Cloudflare runtime example. +- `opengame-api` does not yet expose cast or stream control-plane endpoints. + +## What Still Needs To Be Built + +- a polished public `stream-kit` SDK API +- `opengame-api` stream/cast session endpoints +- a supported integration path from the OGS app to the stream runtime +- docs that distinguish current implementation from target public SDK shape diff --git a/docs/next-steps.md b/docs/next-steps.md new file mode 100644 index 0000000..d9d40a1 --- /dev/null +++ b/docs/next-steps.md @@ -0,0 +1,154 @@ +# Stream Kit Phase 2 Plan + +This document tracks the work that should happen after the Cloudflare proof point is merged. + +## Goal + +Move from: + +- a validated Cloudflare streaming runtime +- a working but low-level example +- partially productized packages + +to: + +- an OGS-owned streaming product +- a clean `stream-kit` SDK for game developers +- a control plane exposed through `opengame-api` +- a first-class integration path through `opengame-app` + +## Phase 2 Priorities + +Work in this order unless a blocker changes priorities. + +### 1. Runtime Hardening + +Goal: make the existing deployed runtime safe to keep running while product work continues. + +Tasks: + +- gate `/ice-servers` behind authenticated session creation +- make sure TURN credentials are minted only for authorized, short-lived sessions +- review `/debug-state` and either remove it from production mode or require stronger auth +- add ownership checks so one receiver cannot inspect or affect another session +- decide what logs stay in production and what becomes debug-only + +Definition of done: + +- no anonymous TURN minting +- no open debug surface +- session isolation enforced at the API boundary + +### 2. Public SDK Design + +Goal: define what third-party developers actually import and use. + +Tasks: + +- define the public API for `@open-game-system/stream-kit` +- define the public API for `@open-game-system/stream-kit-react` +- decide what is React-only versus framework-agnostic +- define fallback behavior inside vs outside `opengame-app` +- decide how much of `app-bridge` remains public, if any + +Target surface: + +- `OGSProvider` +- `CastButton` +- `StreamCanvas` +- `useCastSession()` +- `useTVStream()` +- `createOGSClient()` for non-React users + +Definition of done: + +- written API proposal +- docs examples for React and non-React +- agreement on what is public, internal, and deprecated + +### 3. `opengame-api` Control Plane + +Goal: move the product-facing session orchestration into the OGS backend. + +Tasks: + +- define stream session endpoints +- define cast session endpoints if casting and streaming are distinct products +- add auth, entitlement, and developer/game ownership checks +- add usage accounting and observability +- define the internal contract between `opengame-api` and the runtime + +Likely endpoint family: + +- `POST /api/v1/stream/session` +- `GET /api/v1/stream/session/:id` +- `DELETE /api/v1/stream/session/:id` +- `POST /api/v1/cast/session` +- `GET /api/v1/cast/session/:id` +- `DELETE /api/v1/cast/session/:id` + +Definition of done: + +- control-plane routes exist in `opengame-api` +- they create and manage authorized sessions against OGS-owned runtime infrastructure + +### 4. `opengame-app` Integration + +Goal: make the app the real host of the end-user experience. + +Tasks: + +- wire cast/stream actions from `app-bridge` or `stream-kit` into real backend sessions +- define the session lifecycle in the app +- define how TV mode differs from in-app cloud rendering +- define what the game sees versus what the native app owns + +Definition of done: + +- a web game in `opengame-app` can trigger a real OGS-managed stream/cast flow +- app/session state is reflected back to the web game consistently + +### 5. Test Automation + +Goal: improve confidence without depending only on manual receiver testing. + +Tasks: + +- add stronger seam tests for session creation and authorization +- add CI-safe integration tests around Worker/runtime request plumbing +- add deploy smoke verification for `health`, session creation, and runtime startup +- reduce known warnings in test output where practical + +Definition of done: + +- critical session/auth/runtime seams are covered +- deploy regressions are caught earlier than manual testing + +## Recommended Branching After Merge + +After this PR lands, branch from `main` and tackle the next work in smaller focused branches: + +1. `codex/stream-kit-runtime-hardening` +2. `codex/stream-kit-sdk-design` +3. `codex/opengame-api-stream-control-plane` +4. `codex/opengame-app-stream-integration` + +## Open Questions + +These still need explicit decisions: + +- Should cast mode and cloud render mode be separate products or one combined flow? +- Should `stream-kit` remain partly usable outside `opengame-app`, or fully optimize for the OGS app environment? +- Should the runtime stay as a sibling service/repo concern, or become a formal internal package consumed by `opengame-api`? +- What is the supported receiver model for TV playback long term? + +## What This Repo Should Track + +This `docs/` folder should remain the self-contained source of truth for: + +- current architecture +- verified status +- target SDK shape +- next-step execution plan + +When Phase 2 starts, update this file first, then implement against it. diff --git a/docs/status.md b/docs/status.md new file mode 100644 index 0000000..a975051 --- /dev/null +++ b/docs/status.md @@ -0,0 +1,66 @@ +# Stream Kit Status + +## What Has Been Completed + +Recent verified work in this repo: + +- Cloudflare deployed streaming now works end to end. +- Cloudflare TURN credentials are minted and normalized by the Worker. +- The receiver and sender both use the same ICE configuration. +- Worker tracing and debug tooling were added to diagnose deployed failures. +- Worker seam tests cover TURN normalization, debug endpoint auth, and session routing. +- Session routing now uses a per-receiver session ID instead of a single shared singleton. +- The `pnpm test` hang caused by watch mode in `packages/stream-kit-web` was fixed. + +Relevant commits: + +- `08235c9` Add Cloudflare TURN-backed deployed streaming +- `756091f` Harden Worker boundaries and add seam tests +- `2edf3f4` Isolate streamed sessions per receiver + +## What Is Working Today + +- Local Bun/container example +- Local receiver playback +- Deployed Worker + Durable Object + container boot +- Chromium page navigation inside the container +- WebRTC media delivery in the deployed environment +- PR-scoped Cloudflare preview deployments can be validated with Playwright + before merge +- Manual Chrome receiver validation is still the source of truth for full media + playback; the Playwright PR check currently validates real-infra session + startup rather than rendered-video playback + +## Main Risks / Gaps + +- `/ice-servers` is still public and should be hardened before wider rollout. +- The Cloudflare example is still the source of truth; the reusable package surface is behind it. +- Current docs and package READMEs still need further productization cleanup so they match the desired OGS SDK story. + +## What Needs To Be Done Next + +See also: [`next-steps.md`](./next-steps.md) + +### Productization + +- define the public `stream-kit` SDK API +- decide exactly how `app-bridge` is hidden inside the SDK +- decide whether the stream runtime remains a sibling service or becomes a formal internal package + +### OGS Integration + +- add `opengame-api` endpoints for stream/cast session lifecycle +- define app-to-backend session flow for OGS-owned infrastructure +- define third-party developer configuration and game registration needs + +### Hardening + +- gate `/ice-servers` so TURN credentials cannot be minted anonymously +- decide whether `/debug-state` stays debug-only or is removed in production +- add more seam/integration coverage around stream startup and runtime failures + +### Docs + +- align package-level READMEs with the current OGS direction +- publish a first-class getting started guide for React and non-React games +- document the exact Worker secrets and deployment assumptions for the example diff --git a/examples/basic-react-demo/README.md b/examples/basic-react-demo/README.md new file mode 100644 index 0000000..1e7a0b8 --- /dev/null +++ b/examples/basic-react-demo/README.md @@ -0,0 +1,136 @@ +# Stream Kit React Demo + +A minimal React application demonstrating how to integrate cloud-rendered streams into a web application using the Stream Kit libraries. + +## Overview + +This demo showcases: +- Setting up the Stream Kit client in a React application +- Rendering WebRTC video streams with `StreamCanvas` +- Managing stream state and quality options +- Handling user interactions with streamed content + +## Getting Started + +### Prerequisites + +- Node.js 16+ +- npm or pnpm + +### Installation + +From the project root directory: + +```bash +# Install dependencies +pnpm install + +# Start the development server +pnpm --filter basic-react-demo dev +``` + +Visit http://localhost:5173 to see the demo in action. + +## Key Features + +- **Stream Provider Setup**: Demonstrates how to initialize and provide the Stream Kit client throughout your React application +- **Video Stream Component**: Shows how to embed the cloud-rendered stream using the `StreamCanvas` component +- **State Management**: Displays connection status, quality metrics, and error handling +- **Interaction Handling**: Example of sending user inputs to the streamed application + +## How It Works + +The demo connects to a Stream Kit server endpoint that hosts a rendered application. The video output is streamed to the client via WebRTC while user interactions are sent back to the server. + +### Key Components + +```jsx +// Initialize the client +const streamClient = createStreamClient({ + brokerUrl: 'api.example.com' +}); + +// Provide the client to your app +function App() { + return ( + + + + ); +} + +// Use the StreamCanvas component to display the stream +function DemoContent() { + const [streamState, setStreamState] = useState({ status: 'initializing' }); + + return ( +
+ + +
+ Connection status: {streamState.status} +
+
+ ); +} +``` + +## Configuration + +The demo can be configured by editing the following files: + +- `src/App.tsx`: Main application component with Stream Kit integration +- `src/main.tsx`: Entry point that renders the App component +- `vite.config.ts`: Build and development server configuration + +## Testing + +The demo includes tests demonstrating how to test Stream Kit components: + +```bash +# Run tests +pnpm --filter basic-react-demo test +``` + +## Using with the Bun Stream Server + +This demo can be connected to the `bun-stream-server` example in this repo to create a full end-to-end streaming demo: + +1. Start the Bun Stream Server: + ```bash + cd ../bun-stream-server/container + docker build -t stream-server-test . + docker run -p 8080:8080 --rm stream-server-test + ``` + +2. Configure the React demo to connect to this server by setting the appropriate broker URL in `App.tsx`. You don't need to configure a custom PeerJS server as the extension uses the default PeerJS server automatically. + +3. Start the React app with `pnpm dev` + +The WebRTC connection will be established through the default PeerJS server, so no additional configuration is needed for signaling or peer discovery. + +## Customization + +To adapt this demo for your own projects: + +1. Update the broker URL to point to your Stream Kit server +2. Modify the stream URL to target your own cloud-rendered application +3. Adjust render options based on your performance requirements +4. Implement custom interaction handlers specific to your application + +## Related Resources + +- [Stream Kit Documentation](../../README.md) +- [React Component API](../../packages/stream-kit-react/README.md) +- [Client Library API](../../packages/stream-kit-web/README.md) + +## License + +MIT License \ No newline at end of file diff --git a/examples/basic-react-demo/package.json b/examples/basic-react-demo/package.json index d3603d9..7bf4440 100644 --- a/examples/basic-react-demo/package.json +++ b/examples/basic-react-demo/package.json @@ -16,26 +16,26 @@ "@open-game-system/stream-kit-react": "workspace:*", "@open-game-system/stream-kit-types": "workspace:*", "@open-game-system/stream-kit-web": "workspace:*", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { - "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^14.2.1", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^14.3.1", "@testing-library/user-event": "^14.6.1", - "@types/react": "^18.2.37", - "@types/react-dom": "^18.2.15", - "@types/testing-library__jest-dom": "^5.14.5", - "@typescript-eslint/eslint-plugin": "^8.30.1", - "@typescript-eslint/parser": "^8.30.1", - "@vitejs/plugin-react": "^4.2.0", + "@types/react": "^18.3.24", + "@types/react-dom": "^18.3.7", + "@types/testing-library__jest-dom": "^5.14.9", + "@typescript-eslint/eslint-plugin": "^8.43.0", + "@typescript-eslint/parser": "^8.43.0", + "@vitejs/plugin-react": "^4.7.0", "eslint": "^8.57.1", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.4", - "jsdom": "^23.0.1", - "typescript": "^5.2.2", - "vite": "^5.0.0", - "vitest": "^1.6.0" + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.20", + "jsdom": "^23.2.0", + "typescript": "^5.9.2", + "vite": "^5.4.20", + "vitest": "^1.6.1" } } \ No newline at end of file diff --git a/examples/bun-stream-server/README.md b/examples/bun-stream-server/README.md index d072c5f..22ce27e 100644 --- a/examples/bun-stream-server/README.md +++ b/examples/bun-stream-server/README.md @@ -1,107 +1,59 @@ -# Stream Kit Server Example (Cloudflare Containers) +# Stream Kit Cloudflare Example -This example demonstrates how to run a WebRTC streaming server using Cloudflare Containers, Bun, and Puppeteer. The server can capture browser content and stream it to clients using WebRTC. +This example is the current source of truth for end-to-end Stream Kit behavior. -## Architecture +It demonstrates: -- **Container**: Runs a Bun server with Puppeteer for browser automation and WebRTC streaming -- **Worker**: Manages container lifecycle and routes requests -- **Durable Object**: Maintains container state and handles container-specific operations +- a Cloudflare Worker control plane +- a Durable Object per stream session +- a Cloudflare Container running Chromium +- a Chrome extension that captures the rendered tab +- a browser receiver that plays the returned WebRTC media -## Project Structure +The deployed path now works with Cloudflare TURN-backed connectivity. -``` -. -├── wrangler.toml # Cloudflare Workers/Containers config -├── package.json # Worker dependencies -├── src/ # Worker & Durable Object code -│ ├── index.ts # Worker entry point -│ └── container.ts # Durable Object implementation -├── container/ # Container application code -│ ├── Dockerfile # Container configuration -│ ├── package.json # Container-specific dependencies -│ ├── src/ -│ │ └── server.ts # Bun server with Puppeteer/WebRTC -│ └── tsconfig.json # TypeScript config for container -└── README.md - -``` - -## Code Organization - -The project is split into two main parts: - -1. **Worker & Durable Object** (`/src`): - - Handles routing and container lifecycle - - Implements the external API endpoints - - Manages container state via Durable Objects - -2. **Container Application** (`/container`): - - Runs inside Cloudflare Container - - Implements the actual streaming functionality - - Uses Bun + Puppeteer for browser automation - -## API Routes +## Quick Start -### API Endpoints +### Local receiver + local container -- `GET /health` - Health check endpoint -- `POST /stream` - Create a new stream session -- `GET /stream/:id` - Get stream info and status -- `DELETE /stream/:id` - Stop and cleanup stream - -## Development - -1. Install dependencies for both the Worker and Container: ```bash -# Install Worker dependencies -npm install - -# Install Container dependencies -cd container && npm install +cd examples/bun-stream-server/container +bun install +bun run src/server.ts ``` -2. Test the container locally: -```bash -# Build the container (from the container directory) -cd container -docker build -t stream-server-test . +Then open [`receiver.html`](./receiver.html) and point it at `http://localhost:8080`. -# Run it locally -docker run -p 8080:8080 --rm stream-server-test +### Cloudflare deploy -# Test with curl -curl http://localhost:8080/health -``` - -3. Deploy to Cloudflare: ```bash -# From the root directory -wrangler deploy +cd examples/bun-stream-server +npx wrangler deploy ``` -## Container Details +Required Worker secrets: + +- `CLOUDFLARE_TURN_API_TOKEN` +- `CLOUDFLARE_TURN_KEY_ID` +- optional `DEBUG_STATE_TOKEN` -The container runs: -- Bun for the server runtime -- Puppeteer for browser automation -- Chrome/Chromium for page rendering -- WebRTC for streaming +## Main Endpoints -## Environment Variables +- `GET /health` +- `GET /ice-servers` +- `POST /start-stream` +- `GET /debug-state` when authorized -The container automatically receives these Cloudflare-provided variables: -- `CLOUDFLARE_COUNTRY_A2` - Two-letter country code -- `CLOUDFLARE_LOCATION` - Location name -- `CLOUDFLARE_REGION` - Region name +## Current Notes -## Notes +- The Worker uses `x-stream-session-id` to isolate sessions. +- TURN credentials are short-lived and currently minted for the receiver on demand. +- The receiver is still a debugging/demo page, not yet the final OGS SDK surface. -- Initial container provisioning takes a few minutes -- Each container instance can handle one streaming session -- The Worker will automatically route requests to the appropriate container instance -- Container instances are recycled after periods of inactivity +## Product Direction -## License +This example proves the runtime. The intended OGS product should eventually hide this infrastructure behind: -MIT License +- OGS-owned APIs +- a simpler `stream-kit` SDK +- `opengame-app` integration through `app-bridge` diff --git a/examples/bun-stream-server/container/.dockerignore b/examples/bun-stream-server/container/.dockerignore new file mode 100644 index 0000000..414a7c1 --- /dev/null +++ b/examples/bun-stream-server/container/.dockerignore @@ -0,0 +1,17 @@ +# Node dependencies +node_modules/ + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build output +dist/ +build/ + +# Env files +.env + +# Other +.DS_Store \ No newline at end of file diff --git a/examples/bun-stream-server/container/Dockerfile b/examples/bun-stream-server/container/Dockerfile new file mode 100644 index 0000000..f95e7c5 --- /dev/null +++ b/examples/bun-stream-server/container/Dockerfile @@ -0,0 +1,62 @@ +# Cloudflare Containers require linux/amd64 +FROM --platform=linux/amd64 debian:bullseye-slim + +# Prevent interactive prompts during package installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install Node.js and necessary dependencies +RUN apt-get update && apt-get install -y \ + curl \ + gnupg \ + wget \ + unzip \ + ca-certificates \ + && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ + && apt-get install -y nodejs + +# Install Chromium and dependencies for ARM64 +RUN apt-get update && apt-get install -y \ + chromium \ + chromium-driver \ + fonts-liberation \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libatspi2.0-0 \ + libdrm2 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libxcomposite1 \ + libxdamage1 \ + libxrandr2 \ + xdg-utils \ + && rm -rf /var/lib/apt/lists/* + +# Set Chromium path for Puppeteer +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium + +# Mark that we're running in Docker for Chrome configuration +ENV DOCKER_ENVIRONMENT=true + +# Install tsx for TypeScript execution +RUN npm install -g tsx + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy source and extension +COPY . . + +# Run the app using tsx to handle TypeScript +EXPOSE 8080 +# Expose UDP port range for WebRTC media connections +EXPOSE 40000-40100/udp +CMD ["npm", "start"] \ No newline at end of file diff --git a/examples/bun-stream-server/container/bun.lockb b/examples/bun-stream-server/container/bun.lockb index de0afaa..d115353 100755 Binary files a/examples/bun-stream-server/container/bun.lockb and b/examples/bun-stream-server/container/bun.lockb differ diff --git a/examples/bun-stream-server/container/extension/background.js b/examples/bun-stream-server/container/extension/background.js new file mode 100644 index 0000000..e3f16cf --- /dev/null +++ b/examples/bun-stream-server/container/extension/background.js @@ -0,0 +1,42 @@ +/** + * Chrome Extension Background Script + * + * This background script runs when the extension loads and creates a tab + * that loads our streaming interface. This tab becomes the target that + * Puppeteer will interact with to initiate screen streaming. + * + * FLOW: + * 1. Extension loads → background.js runs + * 2. Creates hidden tab with streaming.html + * 3. streaming.html loads PeerJS and streaming.js + * 4. Puppeteer calls INITIALIZE() function in streaming.js context + * 5. Screen capture and streaming begins + */ + +chrome.runtime.onInstalled.addListener(() => { + chrome.tabs.create({ url: chrome.runtime.getURL('streaming.html'), active: false }); +}); + +chrome.runtime.onStartup.addListener(() => { + chrome.tabs.create({ url: chrome.runtime.getURL('streaming.html'), active: false }); +}); + +chrome.commands.onCommand.addListener(async (command) => { + console.log('[BACKGROUND] Command received:', command); + if (command === 'start-capture') { + // Focus the target tab (current active in current window) + try { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + console.log('[BACKGROUND] Active tab for capture:', tab && { id: tab.id, url: tab.url }); + if (tab && tab.id) { + // Send a message to the streaming page to start capture immediately + // Broadcast to all extension contexts; streaming.html listens on runtime.onMessage + chrome.runtime.sendMessage({ type: 'START_CAPTURE', targetTabId: tab.id }); + console.log('[BACKGROUND] START_CAPTURE runtime message sent'); + } + } catch (e) { + // eslint-disable-next-line no-console + console.warn('[BACKGROUND] start-capture error:', e && e.message); + } + } +}); diff --git a/examples/bun-stream-server/container/extension/content.js b/examples/bun-stream-server/container/extension/content.js new file mode 100644 index 0000000..ce14220 --- /dev/null +++ b/examples/bun-stream-server/container/extension/content.js @@ -0,0 +1,12 @@ +(() => { + try { + // Lightweight marker to indicate the content script ran + window.__videoCaptureExtensionActive = true; + // Optionally, expose a no-op to help debugging + // eslint-disable-next-line no-console + console.log('[CONTENT] Video Capture extension content script injected'); + } catch (e) { + // eslint-disable-next-line no-console + console.warn('[CONTENT] Injection error:', e && e.message); + } +})(); \ No newline at end of file diff --git a/examples/bun-stream-server/container/extension/manifest.json b/examples/bun-stream-server/container/extension/manifest.json new file mode 100644 index 0000000..24bddb4 --- /dev/null +++ b/examples/bun-stream-server/container/extension/manifest.json @@ -0,0 +1,44 @@ +{ + "name": "Video Capture", + "version": "0.1.0", + "key": "ackedhmjjinfocdcekpnbdocpmiffaac", + "manifest_version": 3, + "background": { + "service_worker": "background.js" + }, + "sockets": { + "tcp": { + "connect": ["*"] + } + }, + "permissions": [ + "tabs", + "tabCapture", + "activeTab", + "storage", + "scripting", + "sockets.udp", + "sockets.tcp" + ], + "host_permissions": [ + "*://*/*", + "", + "https://*/*", + "http://*/*" + ], + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"], + "run_at": "document_start" + } + ], + "commands": { + "start-capture": { + "suggested_key": { + "default": "Alt+Shift+S" + }, + "description": "Start tab capture" + } + } +} diff --git a/examples/bun-stream-server/container/extension/peer.js b/examples/bun-stream-server/container/extension/peer.js new file mode 100644 index 0000000..1af32d8 --- /dev/null +++ b/examples/bun-stream-server/container/extension/peer.js @@ -0,0 +1,517 @@ +(()=>{function e(e,t,n,r){Object.defineProperty(e,t,{get:n,set:r,enumerable:!0,configurable:!0})}function t(e){return e&&e.__esModule?e.default:e}class n{constructor(){this.chunkedMTU=16300// The original 60000 bytes setting does not work when sending data from Firefox to Chrome, which is "cut off" after 16384 bytes and delivered individually. +,// Binary stuff +this._dataCount=1,this.chunk=e=>{let t=[],n=e.byteLength,r=Math.ceil(n/this.chunkedMTU),i=0,o=0;for(;o0){let e=new Uint8Array(this._pieces);this._parts.push(e),this._pieces=[]}}toArrayBuffer(){let e=[];for(let t of this._parts)e.push(t);return function(e){let t=0;for(let n of e)t+=n.byteLength;let n=new Uint8Array(t),r=0;for(let t of e){let e=new Uint8Array(t.buffer,t.byteOffset,t.byteLength);n.set(e,r),r+=t.byteLength}return n}(e).buffer}constructor(){this.encoder=new TextEncoder,this._pieces=[],this._parts=[]}}function i(e){let t=new s(e);return t.unpack()}function o(e){let t=new a;return t.pack(e),t.getBuffer()}class s{unpack(){let e;let t=this.unpack_uint8();if(t<128)return t;if((224^t)<32)return(224^t)-32;if((e=160^t)<=15)return this.unpack_raw(e);if((e=176^t)<=15)return this.unpack_string(e);if((e=144^t)<=15)return this.unpack_array(e);if((e=128^t)<=15)return this.unpack_map(e);switch(t){case 192:return null;case 193:case 212:case 213:case 214:case 215:return;case 194:return!1;case 195:return!0;case 202:return this.unpack_float();case 203:return this.unpack_double();case 204:return this.unpack_uint8();case 205:return this.unpack_uint16();case 206:return this.unpack_uint32();case 207:return this.unpack_uint64();case 208:return this.unpack_int8();case 209:return this.unpack_int16();case 210:return this.unpack_int32();case 211:return this.unpack_int64();case 216:return e=this.unpack_uint16(),this.unpack_string(e);case 217:return e=this.unpack_uint32(),this.unpack_string(e);case 218:return e=this.unpack_uint16(),this.unpack_raw(e);case 219:return e=this.unpack_uint32(),this.unpack_raw(e);case 220:return e=this.unpack_uint16(),this.unpack_array(e);case 221:return e=this.unpack_uint32(),this.unpack_array(e);case 222:return e=this.unpack_uint16(),this.unpack_map(e);case 223:return e=this.unpack_uint32(),this.unpack_map(e)}}unpack_uint8(){let e=255&this.dataView[this.index];return this.index++,e}unpack_uint16(){let e=this.read(2),t=(255&e[0])*256+(255&e[1]);return this.index+=2,t}unpack_uint32(){let e=this.read(4),t=((256*e[0]+e[1])*256+e[2])*256+e[3];return this.index+=4,t}unpack_uint64(){let e=this.read(8),t=((((((256*e[0]+e[1])*256+e[2])*256+e[3])*256+e[4])*256+e[5])*256+e[6])*256+e[7];return this.index+=8,t}unpack_int8(){let e=this.unpack_uint8();return e<128?e:e-256}unpack_int16(){let e=this.unpack_uint16();return e<32768?e:e-65536}unpack_int32(){let e=this.unpack_uint32();return e<2147483648?e:e-4294967296}unpack_int64(){let e=this.unpack_uint64();return e<0x7fffffffffffffff?e:e-18446744073709552e3}unpack_raw(e){if(this.length>31?1:-1)*(8388607&e|8388608)*2**((e>>23&255)-127-23)}unpack_double(){let e=this.unpack_uint32(),t=this.unpack_uint32(),n=(e>>20&2047)-1023;return(0==e>>31?1:-1)*((1048575&e|1048576)*2**(n-20)+t*2**(n-52))}read(e){let t=this.index;if(t+e<=this.length)return this.dataView.subarray(t,t+e);throw Error("BinaryPackFailure: read index out of range")}constructor(e){this.index=0,this.dataBuffer=e,this.dataView=new Uint8Array(this.dataBuffer),this.length=this.dataBuffer.byteLength}}class a{getBuffer(){return this._bufferBuilder.toArrayBuffer()}pack(e){if("string"==typeof e)this.pack_string(e);else if("number"==typeof e)Math.floor(e)===e?this.pack_integer(e):this.pack_double(e);else if("boolean"==typeof e)!0===e?this._bufferBuilder.append(195):!1===e&&this._bufferBuilder.append(194);else if(void 0===e)this._bufferBuilder.append(192);else if("object"==typeof e){if(null===e)this._bufferBuilder.append(192);else{let t=e.constructor;if(e instanceof Array)this.pack_array(e);else if(e instanceof ArrayBuffer)this.pack_bin(new Uint8Array(e));else if("BYTES_PER_ELEMENT"in e)this.pack_bin(new Uint8Array(e.buffer,e.byteOffset,e.byteLength));else if(e instanceof Date)this.pack_string(e.toString());else if(t==Object||t.toString().startsWith("class"))this.pack_object(e);else throw Error(`Type "${t.toString()}" not yet supported`)}}else throw Error(`Type "${typeof e}" not yet supported`);this._bufferBuilder.flush()}pack_bin(e){let t=e.length;if(t<=15)this.pack_uint8(160+t);else if(t<=65535)this._bufferBuilder.append(218),this.pack_uint16(t);else if(t<=4294967295)this._bufferBuilder.append(219),this.pack_uint32(t);else throw Error("Invalid length");this._bufferBuilder.append_buffer(e)}pack_string(e){let t=this._textEncoder.encode(e),n=t.length;if(n<=15)this.pack_uint8(176+n);else if(n<=65535)this._bufferBuilder.append(216),this.pack_uint16(n);else if(n<=4294967295)this._bufferBuilder.append(217),this.pack_uint32(n);else throw Error("Invalid length");this._bufferBuilder.append_buffer(t)}pack_array(e){let t=e.length;if(t<=15)this.pack_uint8(144+t);else if(t<=65535)this._bufferBuilder.append(220),this.pack_uint16(t);else if(t<=4294967295)this._bufferBuilder.append(221),this.pack_uint32(t);else throw Error("Invalid length");for(let n=0;n=-32&&e<=127)this._bufferBuilder.append(255&e);else if(e>=0&&e<=255)this._bufferBuilder.append(204),this.pack_uint8(e);else if(e>=-128&&e<=127)this._bufferBuilder.append(208),this.pack_int8(e);else if(e>=0&&e<=65535)this._bufferBuilder.append(205),this.pack_uint16(e);else if(e>=-32768&&e<=32767)this._bufferBuilder.append(209),this.pack_int16(e);else if(e>=0&&e<=4294967295)this._bufferBuilder.append(206),this.pack_uint32(e);else if(e>=-2147483648&&e<=2147483647)this._bufferBuilder.append(210),this.pack_int32(e);else if(e>=-0x8000000000000000&&e<=0x7fffffffffffffff)this._bufferBuilder.append(211),this.pack_int64(e);else if(e>=0&&e<=18446744073709552e3)this._bufferBuilder.append(207),this.pack_uint64(e);else throw Error("Invalid integer")}pack_double(e){let t=0;e<0&&(t=1,e=-e);let n=Math.floor(Math.log(e)/Math.LN2),r=e/2**n-1,i=Math.floor(4503599627370496*r),o=t<<31|n+1023<<20|i/4294967296&1048575;this._bufferBuilder.append(203),this.pack_int32(o),this.pack_int32(i%4294967296)}pack_object(e){let t=Object.keys(e),n=t.length;if(n<=15)this.pack_uint8(128+n);else if(n<=65535)this._bufferBuilder.append(222),this.pack_uint16(n);else if(n<=4294967295)this._bufferBuilder.append(223),this.pack_uint32(n);else throw Error("Invalid length");for(let t in e)e.hasOwnProperty(t)&&(this.pack(t),this.pack(e[t]))}pack_uint8(e){this._bufferBuilder.append(e)}pack_uint16(e){this._bufferBuilder.append(e>>8),this._bufferBuilder.append(255&e)}pack_uint32(e){let t=4294967295&e;this._bufferBuilder.append((4278190080&t)>>>24),this._bufferBuilder.append((16711680&t)>>>16),this._bufferBuilder.append((65280&t)>>>8),this._bufferBuilder.append(255&t)}pack_uint64(e){let t=e/4294967296,n=e%4294967296;this._bufferBuilder.append((4278190080&t)>>>24),this._bufferBuilder.append((16711680&t)>>>16),this._bufferBuilder.append((65280&t)>>>8),this._bufferBuilder.append(255&t),this._bufferBuilder.append((4278190080&n)>>>24),this._bufferBuilder.append((16711680&n)>>>16),this._bufferBuilder.append((65280&n)>>>8),this._bufferBuilder.append(255&n)}pack_int8(e){this._bufferBuilder.append(255&e)}pack_int16(e){this._bufferBuilder.append((65280&e)>>8),this._bufferBuilder.append(255&e)}pack_int32(e){this._bufferBuilder.append(e>>>24&255),this._bufferBuilder.append((16711680&e)>>>16),this._bufferBuilder.append((65280&e)>>>8),this._bufferBuilder.append(255&e)}pack_int64(e){let t=Math.floor(e/4294967296),n=e%4294967296;this._bufferBuilder.append((4278190080&t)>>>24),this._bufferBuilder.append((16711680&t)>>>16),this._bufferBuilder.append((65280&t)>>>8),this._bufferBuilder.append(255&t),this._bufferBuilder.append((4278190080&n)>>>24),this._bufferBuilder.append((16711680&n)>>>16),this._bufferBuilder.append((65280&n)>>>8),this._bufferBuilder.append(255&n)}constructor(){this._bufferBuilder=new r,this._textEncoder=new TextEncoder}}let c=!0,l=!0;function p(e,t,n){let r=e.match(t);return r&&r.length>=n&&parseInt(r[n],10)}function d(e,t,n){if(!e.RTCPeerConnection)return;let r=e.RTCPeerConnection.prototype,i=r.addEventListener;r.addEventListener=function(e,r){if(e!==t)return i.apply(this,arguments);let o=e=>{let t=n(e);t&&(r.handleEvent?r.handleEvent(t):r(t))};return this._eventMap=this._eventMap||{},this._eventMap[t]||(this._eventMap[t]=new Map),this._eventMap[t].set(r,o),i.apply(this,[e,o])};let o=r.removeEventListener;r.removeEventListener=function(e,n){if(e!==t||!this._eventMap||!this._eventMap[t]||!this._eventMap[t].has(n))return o.apply(this,arguments);let r=this._eventMap[t].get(n);return this._eventMap[t].delete(n),0===this._eventMap[t].size&&delete this._eventMap[t],0===Object.keys(this._eventMap).length&&delete this._eventMap,o.apply(this,[e,r])},Object.defineProperty(r,"on"+t,{get(){return this["_on"+t]},set(e){this["_on"+t]&&(this.removeEventListener(t,this["_on"+t]),delete this["_on"+t]),e&&this.addEventListener(t,this["_on"+t]=e)},enumerable:!0,configurable:!0})}function h(e){return"boolean"!=typeof e?Error("Argument type: "+typeof e+". Please use a boolean."):(c=e,e?"adapter.js logging disabled":"adapter.js logging enabled")}function u(e){return"boolean"!=typeof e?Error("Argument type: "+typeof e+". Please use a boolean."):(l=!e,"adapter.js deprecation warnings "+(e?"disabled":"enabled"))}function f(){"object"!=typeof window||c||"undefined"==typeof console||"function"!=typeof console.log||console.log.apply(console,arguments)}function m(e,t){l&&console.warn(e+" is deprecated, please use "+t+" instead.")}/** + * Checks if something is an object. + * + * @param {*} val The something you want to check. + * @return true if val is an object, false otherwise. + */function g(e){return"[object Object]"===Object.prototype.toString.call(e)}function y(e,t,n){let r=n?"outbound-rtp":"inbound-rtp",i=new Map;if(null===t)return i;let o=[];return e.forEach(e=>{"track"===e.type&&e.trackIdentifier===t.id&&o.push(e)}),o.forEach(t=>{e.forEach(n=>{n.type===r&&n.trackId===t.id&&function e(t,n,r){!n||r.has(n.id)||(r.set(n.id,n),Object.keys(n).forEach(i=>{i.endsWith("Id")?e(t,t.get(n[i]),r):i.endsWith("Ids")&&n[i].forEach(n=>{e(t,t.get(n),r)})}))}(e,n,i)})}),i}var _,C,v,b,k,S,T,R,w,P,E,D,x,I,M,O,j={};function L(e,t){let n=e&&e.navigator;if(!n.mediaDevices)return;let r=function(e){if("object"!=typeof e||e.mandatory||e.optional)return e;let t={};return Object.keys(e).forEach(n=>{if("require"===n||"advanced"===n||"mediaSource"===n)return;let r="object"==typeof e[n]?e[n]:{ideal:e[n]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);let i=function(e,t){return e?e+t.charAt(0).toUpperCase()+t.slice(1):"deviceId"===t?"sourceId":t};if(void 0!==r.ideal){t.optional=t.optional||[];let e={};"number"==typeof r.ideal?(e[i("min",n)]=r.ideal,t.optional.push(e),(e={})[i("max",n)]=r.ideal):e[i("",n)]=r.ideal,t.optional.push(e)}void 0!==r.exact&&"number"!=typeof r.exact?(t.mandatory=t.mandatory||{},t.mandatory[i("",n)]=r.exact):["min","max"].forEach(e=>{void 0!==r[e]&&(t.mandatory=t.mandatory||{},t.mandatory[i(e,n)]=r[e])})}),e.advanced&&(t.optional=(t.optional||[]).concat(e.advanced)),t},i=function(e,i){if(t.version>=61)return i(e);if((e=JSON.parse(JSON.stringify(e)))&&"object"==typeof e.audio){let t=function(e,t,n){t in e&&!(n in e)&&(e[n]=e[t],delete e[t])};t((e=JSON.parse(JSON.stringify(e))).audio,"autoGainControl","googAutoGainControl"),t(e.audio,"noiseSuppression","googNoiseSuppression"),e.audio=r(e.audio)}if(e&&"object"==typeof e.video){// Shim facingMode for mobile & surface pro. +let o=e.video.facingMode;o=o&&("object"==typeof o?o:{ideal:o});let s=t.version<66;if(o&&("user"===o.exact||"environment"===o.exact||"user"===o.ideal||"environment"===o.ideal)&&!(n.mediaDevices.getSupportedConstraints&&n.mediaDevices.getSupportedConstraints().facingMode&&!s)){let t;if(delete e.video.facingMode,"environment"===o.exact||"environment"===o.ideal?t=["back","rear"]:("user"===o.exact||"user"===o.ideal)&&(t=["front"]),t)return n.mediaDevices.enumerateDevices().then(n=>{let s=(n=n.filter(e=>"videoinput"===e.kind)).find(e=>t.some(t=>e.label.toLowerCase().includes(t)));return!s&&n.length&&t.includes("back")&&(s=n[n.length-1]),s&&(e.video.deviceId=o.exact?{exact:s.deviceId}:{ideal:s.deviceId}),e.video=r(e.video),f("chrome: "+JSON.stringify(e)),i(e)})}e.video=r(e.video)}return f("chrome: "+JSON.stringify(e)),i(e)},o=function(e){return t.version>=64?e:{name:({PermissionDeniedError:"NotAllowedError",PermissionDismissedError:"NotAllowedError",InvalidStateError:"NotAllowedError",DevicesNotFoundError:"NotFoundError",ConstraintNotSatisfiedError:"OverconstrainedError",TrackStartError:"NotReadableError",MediaDeviceFailedDueToShutdown:"NotAllowedError",MediaDeviceKillSwitchOn:"NotAllowedError",TabCaptureError:"AbortError",ScreenCaptureError:"AbortError",DeviceCaptureError:"AbortError"})[e.name]||e.name,message:e.message,constraint:e.constraint||e.constraintName,toString(){return this.name+(this.message&&": ")+this.message}}};// Even though Chrome 45 has navigator.mediaDevices and a getUserMedia +// function which returns a Promise, it does not accept spec-style +// constraints. +if(n.getUserMedia=(function(e,t,r){i(e,e=>{n.webkitGetUserMedia(e,t,e=>{r&&r(o(e))})})}).bind(n),n.mediaDevices.getUserMedia){let e=n.mediaDevices.getUserMedia.bind(n.mediaDevices);n.mediaDevices.getUserMedia=function(t){return i(t,t=>e(t).then(e=>{if(t.audio&&!e.getAudioTracks().length||t.video&&!e.getVideoTracks().length)throw e.getTracks().forEach(e=>{e.stop()}),new DOMException("","NotFoundError");return e},e=>Promise.reject(o(e))))}}}function A(e,t){if((!e.navigator.mediaDevices||!("getDisplayMedia"in e.navigator.mediaDevices))&&e.navigator.mediaDevices){// getSourceId is a function that returns a promise resolving with +// the sourceId of the screen/window/tab to be shared. +if("function"!=typeof t){console.error("shimGetDisplayMedia: getSourceId argument is not a function");return}e.navigator.mediaDevices.getDisplayMedia=function(n){return t(n).then(t=>{let r=n.video&&n.video.width,i=n.video&&n.video.height,o=n.video&&n.video.frameRate;return n.video={mandatory:{chromeMediaSource:"desktop",chromeMediaSourceId:t,maxFrameRate:o||3}},r&&(n.video.mandatory.maxWidth=r),i&&(n.video.mandatory.maxHeight=i),e.navigator.mediaDevices.getUserMedia(n)})}}}function B(e){e.MediaStream=e.MediaStream||e.webkitMediaStream}function F(e){if("object"!=typeof e||!e.RTCPeerConnection||"ontrack"in e.RTCPeerConnection.prototype)// emitted in unified-plan. Unfortunately this means we need +// to unconditionally wrap the event. +d(e,"track",e=>(e.transceiver||Object.defineProperty(e,"transceiver",{value:{receiver:e.receiver}}),e));else{Object.defineProperty(e.RTCPeerConnection.prototype,"ontrack",{get(){return this._ontrack},set(e){this._ontrack&&this.removeEventListener("track",this._ontrack),this.addEventListener("track",this._ontrack=e)},enumerable:!0,configurable:!0});let t=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(){return this._ontrackpoly||(this._ontrackpoly=t=>{// onaddstream does not fire when a track is added to an existing +// stream. But stream.onaddtrack is implemented so we use that. +t.stream.addEventListener("addtrack",n=>{let r;r=e.RTCPeerConnection.prototype.getReceivers?this.getReceivers().find(e=>e.track&&e.track.id===n.track.id):{track:n.track};let i=new Event("track");i.track=n.track,i.receiver=r,i.transceiver={receiver:r},i.streams=[t.stream],this.dispatchEvent(i)}),t.stream.getTracks().forEach(n=>{let r;r=e.RTCPeerConnection.prototype.getReceivers?this.getReceivers().find(e=>e.track&&e.track.id===n.id):{track:n};let i=new Event("track");i.track=n,i.receiver=r,i.transceiver={receiver:r},i.streams=[t.stream],this.dispatchEvent(i)})},this.addEventListener("addstream",this._ontrackpoly)),t.apply(this,arguments)}}}function z(e){// Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. +if("object"==typeof e&&e.RTCPeerConnection&&!("getSenders"in e.RTCPeerConnection.prototype)&&"createDTMFSender"in e.RTCPeerConnection.prototype){let t=function(e,t){return{track:t,get dtmf(){return void 0===this._dtmf&&("audio"===t.kind?this._dtmf=e.createDTMFSender(t):this._dtmf=null),this._dtmf},_pc:e}};// augment addTrack when getSenders is not available. +if(!e.RTCPeerConnection.prototype.getSenders){e.RTCPeerConnection.prototype.getSenders=function(){return this._senders=this._senders||[],this._senders.slice();// return a copy of the internal state. +};let n=e.RTCPeerConnection.prototype.addTrack;e.RTCPeerConnection.prototype.addTrack=function(e,r){let i=n.apply(this,arguments);return i||(i=t(this,e),this._senders.push(i)),i};let r=e.RTCPeerConnection.prototype.removeTrack;e.RTCPeerConnection.prototype.removeTrack=function(e){r.apply(this,arguments);let t=this._senders.indexOf(e);-1!==t&&this._senders.splice(t,1)}}let n=e.RTCPeerConnection.prototype.addStream;e.RTCPeerConnection.prototype.addStream=function(e){this._senders=this._senders||[],n.apply(this,[e]),e.getTracks().forEach(e=>{this._senders.push(t(this,e))})};let r=e.RTCPeerConnection.prototype.removeStream;e.RTCPeerConnection.prototype.removeStream=function(e){this._senders=this._senders||[],r.apply(this,[e]),e.getTracks().forEach(e=>{let t=this._senders.find(t=>t.track===e);t&&this._senders.splice(this._senders.indexOf(t),1)})}}else if("object"==typeof e&&e.RTCPeerConnection&&"getSenders"in e.RTCPeerConnection.prototype&&"createDTMFSender"in e.RTCPeerConnection.prototype&&e.RTCRtpSender&&!("dtmf"in e.RTCRtpSender.prototype)){let t=e.RTCPeerConnection.prototype.getSenders;e.RTCPeerConnection.prototype.getSenders=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e},Object.defineProperty(e.RTCRtpSender.prototype,"dtmf",{get(){return void 0===this._dtmf&&("audio"===this.track.kind?this._dtmf=this._pc.createDTMFSender(this.track):this._dtmf=null),this._dtmf}})}}function U(e){if(!e.RTCPeerConnection)return;let t=e.RTCPeerConnection.prototype.getStats;e.RTCPeerConnection.prototype.getStats=function(){let[e,n,r]=arguments;// If selector is a function then we are in the old style stats so just +// pass back the original getStats format to avoid breaking old users. +if(arguments.length>0&&"function"==typeof e)return t.apply(this,arguments);// When spec-style getStats is supported, return those when called with +// either no arguments or the selector argument is null. +if(0===t.length&&(0==arguments.length||"function"!=typeof e))return t.apply(this,[]);let i=function(e){let t={},n=e.result();return n.forEach(e=>{let n={id:e.id,timestamp:e.timestamp,type:{localcandidate:"local-candidate",remotecandidate:"remote-candidate"}[e.type]||e.type};e.names().forEach(t=>{n[t]=e.stat(t)}),t[n.id]=n}),t},o=function(e){return new Map(Object.keys(e).map(t=>[t,e[t]]))};return arguments.length>=2?t.apply(this,[function(e){n(o(i(e)))},e]):new Promise((e,n)=>{t.apply(this,[function(t){e(o(i(t)))},n])}).then(n,r)}}function N(e){if(!("object"==typeof e&&e.RTCPeerConnection&&e.RTCRtpSender&&e.RTCRtpReceiver))return;// shim sender stats. +if(!("getStats"in e.RTCRtpSender.prototype)){let t=e.RTCPeerConnection.prototype.getSenders;t&&(e.RTCPeerConnection.prototype.getSenders=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e});let n=e.RTCPeerConnection.prototype.addTrack;n&&(e.RTCPeerConnection.prototype.addTrack=function(){let e=n.apply(this,arguments);return e._pc=this,e}),e.RTCRtpSender.prototype.getStats=function(){let e=this;return this._pc.getStats().then(t=>/* Note: this will include stats of all senders that + * send a track with the same id as sender.track as + * it is not possible to identify the RTCRtpSender. + */y(t,e.track,!0))}}// shim receiver stats. +if(!("getStats"in e.RTCRtpReceiver.prototype)){let t=e.RTCPeerConnection.prototype.getReceivers;t&&(e.RTCPeerConnection.prototype.getReceivers=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e}),d(e,"track",e=>(e.receiver._pc=e.srcElement,e)),e.RTCRtpReceiver.prototype.getStats=function(){let e=this;return this._pc.getStats().then(t=>y(t,e.track,!1))}}if(!("getStats"in e.RTCRtpSender.prototype&&"getStats"in e.RTCRtpReceiver.prototype))return;// shim RTCPeerConnection.getStats(track). +let t=e.RTCPeerConnection.prototype.getStats;e.RTCPeerConnection.prototype.getStats=function(){if(arguments.length>0&&arguments[0]instanceof e.MediaStreamTrack){let e,t,n;let r=arguments[0];return(this.getSenders().forEach(t=>{t.track===r&&(e?n=!0:e=t)}),this.getReceivers().forEach(e=>(e.track===r&&(t?n=!0:t=e),e.track===r)),n||e&&t)?Promise.reject(new DOMException("There are more than one sender or receiver for the track.","InvalidAccessError")):e?e.getStats():t?t.getStats():Promise.reject(new DOMException("There is no sender or receiver for the track.","InvalidAccessError"))}return t.apply(this,arguments)}}function $(e){// shim addTrack/removeTrack with native variants in order to make +// the interactions with legacy getLocalStreams behave as in other browsers. +// Keeps a mapping stream.id => [stream, rtpsenders...] +e.RTCPeerConnection.prototype.getLocalStreams=function(){return this._shimmedLocalStreams=this._shimmedLocalStreams||{},Object.keys(this._shimmedLocalStreams).map(e=>this._shimmedLocalStreams[e][0])};let t=e.RTCPeerConnection.prototype.addTrack;e.RTCPeerConnection.prototype.addTrack=function(e,n){if(!n)return t.apply(this,arguments);this._shimmedLocalStreams=this._shimmedLocalStreams||{};let r=t.apply(this,arguments);return this._shimmedLocalStreams[n.id]?-1===this._shimmedLocalStreams[n.id].indexOf(r)&&this._shimmedLocalStreams[n.id].push(r):this._shimmedLocalStreams[n.id]=[n,r],r};let n=e.RTCPeerConnection.prototype.addStream;e.RTCPeerConnection.prototype.addStream=function(e){this._shimmedLocalStreams=this._shimmedLocalStreams||{},e.getTracks().forEach(e=>{let t=this.getSenders().find(t=>t.track===e);if(t)throw new DOMException("Track already exists.","InvalidAccessError")});let t=this.getSenders();n.apply(this,arguments);let r=this.getSenders().filter(e=>-1===t.indexOf(e));this._shimmedLocalStreams[e.id]=[e].concat(r)};let r=e.RTCPeerConnection.prototype.removeStream;e.RTCPeerConnection.prototype.removeStream=function(e){return this._shimmedLocalStreams=this._shimmedLocalStreams||{},delete this._shimmedLocalStreams[e.id],r.apply(this,arguments)};let i=e.RTCPeerConnection.prototype.removeTrack;e.RTCPeerConnection.prototype.removeTrack=function(e){return this._shimmedLocalStreams=this._shimmedLocalStreams||{},e&&Object.keys(this._shimmedLocalStreams).forEach(t=>{let n=this._shimmedLocalStreams[t].indexOf(e);-1!==n&&this._shimmedLocalStreams[t].splice(n,1),1===this._shimmedLocalStreams[t].length&&delete this._shimmedLocalStreams[t]}),i.apply(this,arguments)}}function J(e,t){if(!e.RTCPeerConnection)return;// shim addTrack and removeTrack. +if(e.RTCPeerConnection.prototype.addTrack&&t.version>=65)return $(e);// also shim pc.getLocalStreams when addTrack is shimmed +// to return the original streams. +let n=e.RTCPeerConnection.prototype.getLocalStreams;e.RTCPeerConnection.prototype.getLocalStreams=function(){let e=n.apply(this);return this._reverseStreams=this._reverseStreams||{},e.map(e=>this._reverseStreams[e.id])};let r=e.RTCPeerConnection.prototype.addStream;e.RTCPeerConnection.prototype.addStream=function(t){// Add identity mapping for consistency with addTrack. +// Unless this is being used with a stream from addTrack. +if(this._streams=this._streams||{},this._reverseStreams=this._reverseStreams||{},t.getTracks().forEach(e=>{let t=this.getSenders().find(t=>t.track===e);if(t)throw new DOMException("Track already exists.","InvalidAccessError")}),!this._reverseStreams[t.id]){let n=new e.MediaStream(t.getTracks());this._streams[t.id]=n,this._reverseStreams[n.id]=t,t=n}r.apply(this,[t])};let i=e.RTCPeerConnection.prototype.removeStream;// replace the internal stream id with the external one and +// vice versa. +function o(e,t){let n=t.sdp;return Object.keys(e._reverseStreams||[]).forEach(t=>{let r=e._reverseStreams[t],i=e._streams[r.id];n=n.replace(RegExp(i.id,"g"),r.id)}),new RTCSessionDescription({type:t.type,sdp:n})}e.RTCPeerConnection.prototype.removeStream=function(e){this._streams=this._streams||{},this._reverseStreams=this._reverseStreams||{},i.apply(this,[this._streams[e.id]||e]),delete this._reverseStreams[this._streams[e.id]?this._streams[e.id].id:e.id],delete this._streams[e.id]},e.RTCPeerConnection.prototype.addTrack=function(t,n){if("closed"===this.signalingState)throw new DOMException("The RTCPeerConnection's signalingState is 'closed'.","InvalidStateError");let r=[].slice.call(arguments,1);if(1!==r.length||!r[0].getTracks().find(e=>e===t))// [[associated MediaStreams]] internal slot. +throw new DOMException("The adapter.js addTrack polyfill only supports a single stream which is associated with the specified track.","NotSupportedError");let i=this.getSenders().find(e=>e.track===t);if(i)throw new DOMException("Track already exists.","InvalidAccessError");this._streams=this._streams||{},this._reverseStreams=this._reverseStreams||{};let o=this._streams[n.id];if(o)// this is using odd Chrome behaviour, use with caution: +// https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 +// Note: we rely on the high-level addTrack/dtmf shim to +// create the sender with a dtmf sender. +o.addTrack(t),// Trigger ONN async. +Promise.resolve().then(()=>{this.dispatchEvent(new Event("negotiationneeded"))});else{let r=new e.MediaStream([t]);this._streams[n.id]=r,this._reverseStreams[r.id]=n,this.addStream(r)}return this.getSenders().find(e=>e.track===t)},["createOffer","createAnswer"].forEach(function(t){let n=e.RTCPeerConnection.prototype[t];e.RTCPeerConnection.prototype[t]=({[t](){let e=arguments,t=arguments.length&&"function"==typeof arguments[0];return t?n.apply(this,[t=>{let n=o(this,t);e[0].apply(null,[n])},t=>{e[1]&&e[1].apply(null,t)},arguments[2]]):n.apply(this,arguments).then(e=>o(this,e))}})[t]});let s=e.RTCPeerConnection.prototype.setLocalDescription;e.RTCPeerConnection.prototype.setLocalDescription=function(){var e,t;let n;return arguments.length&&arguments[0].type&&(arguments[0]=(e=this,t=arguments[0],n=t.sdp,Object.keys(e._reverseStreams||[]).forEach(t=>{let r=e._reverseStreams[t],i=e._streams[r.id];n=n.replace(RegExp(r.id,"g"),i.id)}),new RTCSessionDescription({type:t.type,sdp:n}))),s.apply(this,arguments)};// TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier +let a=Object.getOwnPropertyDescriptor(e.RTCPeerConnection.prototype,"localDescription");Object.defineProperty(e.RTCPeerConnection.prototype,"localDescription",{get(){let e=a.get.apply(this);return""===e.type?e:o(this,e)}}),e.RTCPeerConnection.prototype.removeTrack=function(e){let t;if("closed"===this.signalingState)throw new DOMException("The RTCPeerConnection's signalingState is 'closed'.","InvalidStateError");// We can not yet check for sender instanceof RTCRtpSender +// since we shim RTPSender. So we check if sender._pc is set. +if(!e._pc)throw new DOMException("Argument 1 of RTCPeerConnection.removeTrack does not implement interface RTCRtpSender.","TypeError");let n=e._pc===this;if(!n)throw new DOMException("Sender was not created by this connection.","InvalidAccessError");// Search for the native stream the senders track belongs to. +this._streams=this._streams||{},Object.keys(this._streams).forEach(n=>{let r=this._streams[n].getTracks().find(t=>e.track===t);r&&(t=this._streams[n])}),t&&(1===t.getTracks().length?// takes care of any shimmed _senders. +this.removeStream(this._reverseStreams[t.id]):t.removeTrack(e.track),this.dispatchEvent(new Event("negotiationneeded")))}}function V(e,t){!e.RTCPeerConnection&&e.webkitRTCPeerConnection&&(e.RTCPeerConnection=e.webkitRTCPeerConnection),e.RTCPeerConnection&&t.version<53&&["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(t){let n=e.RTCPeerConnection.prototype[t];e.RTCPeerConnection.prototype[t]=({[t](){return arguments[0]=new("addIceCandidate"===t?e.RTCIceCandidate:e.RTCSessionDescription)(arguments[0]),n.apply(this,arguments)}})[t]})}function G(e,t){d(e,"negotiationneeded",e=>{let n=e.target;if(!(t.version<72)&&(!n.getConfiguration||"plan-b"!==n.getConfiguration().sdpSemantics)||"stable"===n.signalingState)return e})}e(j,"shimMediaStream",()=>B),e(j,"shimOnTrack",()=>F),e(j,"shimGetSendersWithDtmf",()=>z),e(j,"shimGetStats",()=>U),e(j,"shimSenderReceiverGetStats",()=>N),e(j,"shimAddTrackRemoveTrackWithNative",()=>$),e(j,"shimAddTrackRemoveTrack",()=>J),e(j,"shimPeerConnection",()=>V),e(j,"fixNegotiationNeeded",()=>G),e(j,"shimGetUserMedia",()=>L),e(j,"shimGetDisplayMedia",()=>A);var W={};function H(e,t){let n=e&&e.navigator,r=e&&e.MediaStreamTrack;if(n.getUserMedia=function(e,t,r){// Replace Firefox 44+'s deprecation warning with unprefixed version. +m("navigator.getUserMedia","navigator.mediaDevices.getUserMedia"),n.mediaDevices.getUserMedia(e).then(t,r)},!(t.version>55&&"autoGainControl"in n.mediaDevices.getSupportedConstraints())){let e=function(e,t,n){t in e&&!(n in e)&&(e[n]=e[t],delete e[t])},t=n.mediaDevices.getUserMedia.bind(n.mediaDevices);if(n.mediaDevices.getUserMedia=function(n){return"object"==typeof n&&"object"==typeof n.audio&&(e((n=JSON.parse(JSON.stringify(n))).audio,"autoGainControl","mozAutoGainControl"),e(n.audio,"noiseSuppression","mozNoiseSuppression")),t(n)},r&&r.prototype.getSettings){let t=r.prototype.getSettings;r.prototype.getSettings=function(){let n=t.apply(this,arguments);return e(n,"mozAutoGainControl","autoGainControl"),e(n,"mozNoiseSuppression","noiseSuppression"),n}}if(r&&r.prototype.applyConstraints){let t=r.prototype.applyConstraints;r.prototype.applyConstraints=function(n){return"audio"===this.kind&&"object"==typeof n&&(e(n=JSON.parse(JSON.stringify(n)),"autoGainControl","mozAutoGainControl"),e(n,"noiseSuppression","mozNoiseSuppression")),t.apply(this,[n])}}}}function Y(e,t){e.navigator.mediaDevices&&"getDisplayMedia"in e.navigator.mediaDevices||!e.navigator.mediaDevices||(e.navigator.mediaDevices.getDisplayMedia=function(n){if(!(n&&n.video)){let e=new DOMException("getDisplayMedia without video constraints is undefined");return e.name="NotFoundError",// from https://heycam.github.io/webidl/#idl-DOMException-error-names +e.code=8,Promise.reject(e)}return!0===n.video?n.video={mediaSource:t}:n.video.mediaSource=t,e.navigator.mediaDevices.getUserMedia(n)})}function K(e){"object"==typeof e&&e.RTCTrackEvent&&"receiver"in e.RTCTrackEvent.prototype&&!("transceiver"in e.RTCTrackEvent.prototype)&&Object.defineProperty(e.RTCTrackEvent.prototype,"transceiver",{get(){return{receiver:this.receiver}}})}function X(e,t){if("object"!=typeof e||!(e.RTCPeerConnection||e.mozRTCPeerConnection))return;// probably media.peerconnection.enabled=false in about:config +!e.RTCPeerConnection&&e.mozRTCPeerConnection&&(e.RTCPeerConnection=e.mozRTCPeerConnection),t.version<53&&["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(t){let n=e.RTCPeerConnection.prototype[t];e.RTCPeerConnection.prototype[t]=({[t](){return arguments[0]=new("addIceCandidate"===t?e.RTCIceCandidate:e.RTCSessionDescription)(arguments[0]),n.apply(this,arguments)}})[t]});let n={inboundrtp:"inbound-rtp",outboundrtp:"outbound-rtp",candidatepair:"candidate-pair",localcandidate:"local-candidate",remotecandidate:"remote-candidate"},r=e.RTCPeerConnection.prototype.getStats;e.RTCPeerConnection.prototype.getStats=function(){let[e,i,o]=arguments;return r.apply(this,[e||null]).then(e=>{if(t.version<53&&!i)// Leave callback version alone; misc old uses of forEach before Map +try{e.forEach(e=>{e.type=n[e.type]||e.type})}catch(t){if("TypeError"!==t.name)throw t;// Avoid TypeError: "type" is read-only, in old versions. 34-43ish +e.forEach((t,r)=>{e.set(r,Object.assign({},t,{type:n[t.type]||t.type}))})}return e}).then(i,o)}}function q(e){if(!("object"==typeof e&&e.RTCPeerConnection&&e.RTCRtpSender)||e.RTCRtpSender&&"getStats"in e.RTCRtpSender.prototype)return;let t=e.RTCPeerConnection.prototype.getSenders;t&&(e.RTCPeerConnection.prototype.getSenders=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e});let n=e.RTCPeerConnection.prototype.addTrack;n&&(e.RTCPeerConnection.prototype.addTrack=function(){let e=n.apply(this,arguments);return e._pc=this,e}),e.RTCRtpSender.prototype.getStats=function(){return this.track?this._pc.getStats(this.track):Promise.resolve(new Map)}}function Q(e){if(!("object"==typeof e&&e.RTCPeerConnection&&e.RTCRtpSender)||e.RTCRtpSender&&"getStats"in e.RTCRtpReceiver.prototype)return;let t=e.RTCPeerConnection.prototype.getReceivers;t&&(e.RTCPeerConnection.prototype.getReceivers=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e}),d(e,"track",e=>(e.receiver._pc=e.srcElement,e)),e.RTCRtpReceiver.prototype.getStats=function(){return this._pc.getStats(this.track)}}function Z(e){!e.RTCPeerConnection||"removeStream"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.removeStream=function(e){m("removeStream","removeTrack"),this.getSenders().forEach(t=>{t.track&&e.getTracks().includes(t.track)&&this.removeTrack(t)})})}function ee(e){e.DataChannel&&!e.RTCDataChannel&&(e.RTCDataChannel=e.DataChannel)}function et(e){// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 +// Firefox ignores the init sendEncodings options passed to addTransceiver +// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 +if(!("object"==typeof e&&e.RTCPeerConnection))return;let t=e.RTCPeerConnection.prototype.addTransceiver;t&&(e.RTCPeerConnection.prototype.addTransceiver=function(){this.setParametersPromises=[];// WebIDL input coercion and validation +let e=arguments[1]&&arguments[1].sendEncodings;void 0===e&&(e=[]),e=[...e];let n=e.length>0;n&&e.forEach(e=>{if("rid"in e&&!/^[a-z0-9]{0,16}$/i.test(e.rid))throw TypeError("Invalid RID value provided.");if("scaleResolutionDownBy"in e&&!(parseFloat(e.scaleResolutionDownBy)>=1))throw RangeError("scale_resolution_down_by must be >= 1.0");if("maxFramerate"in e&&!(parseFloat(e.maxFramerate)>=0))throw RangeError("max_framerate must be >= 0.0")});let r=t.apply(this,arguments);if(n){// Check if the init options were applied. If not we do this in an +// asynchronous way and save the promise reference in a global object. +// This is an ugly hack, but at the same time is way more robust than +// checking the sender parameters before and after the createOffer +// Also note that after the createoffer we are not 100% sure that +// the params were asynchronously applied so we might miss the +// opportunity to recreate offer. +let{sender:t}=r,n=t.getParameters();"encodings"in n&&// Avoid being fooled by patched getParameters() below. +(1!==n.encodings.length||0!==Object.keys(n.encodings[0]).length)||(n.encodings=e,t.sendEncodings=e,this.setParametersPromises.push(t.setParameters(n).then(()=>{delete t.sendEncodings}).catch(()=>{delete t.sendEncodings})))}return r})}function en(e){if(!("object"==typeof e&&e.RTCRtpSender))return;let t=e.RTCRtpSender.prototype.getParameters;t&&(e.RTCRtpSender.prototype.getParameters=function(){let e=t.apply(this,arguments);return"encodings"in e||(e.encodings=[].concat(this.sendEncodings||[{}])),e})}function er(e){// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 +// Firefox ignores the init sendEncodings options passed to addTransceiver +// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 +if(!("object"==typeof e&&e.RTCPeerConnection))return;let t=e.RTCPeerConnection.prototype.createOffer;e.RTCPeerConnection.prototype.createOffer=function(){return this.setParametersPromises&&this.setParametersPromises.length?Promise.all(this.setParametersPromises).then(()=>t.apply(this,arguments)).finally(()=>{this.setParametersPromises=[]}):t.apply(this,arguments)}}function ei(e){// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 +// Firefox ignores the init sendEncodings options passed to addTransceiver +// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 +if(!("object"==typeof e&&e.RTCPeerConnection))return;let t=e.RTCPeerConnection.prototype.createAnswer;e.RTCPeerConnection.prototype.createAnswer=function(){return this.setParametersPromises&&this.setParametersPromises.length?Promise.all(this.setParametersPromises).then(()=>t.apply(this,arguments)).finally(()=>{this.setParametersPromises=[]}):t.apply(this,arguments)}}e(W,"shimOnTrack",()=>K),e(W,"shimPeerConnection",()=>X),e(W,"shimSenderGetStats",()=>q),e(W,"shimReceiverGetStats",()=>Q),e(W,"shimRemoveStream",()=>Z),e(W,"shimRTCDataChannel",()=>ee),e(W,"shimAddTransceiver",()=>et),e(W,"shimGetParameters",()=>en),e(W,"shimCreateOffer",()=>er),e(W,"shimCreateAnswer",()=>ei),e(W,"shimGetUserMedia",()=>H),e(W,"shimGetDisplayMedia",()=>Y);var eo={};function es(e){if("object"==typeof e&&e.RTCPeerConnection){if("getLocalStreams"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.getLocalStreams=function(){return this._localStreams||(this._localStreams=[]),this._localStreams}),!("addStream"in e.RTCPeerConnection.prototype)){let t=e.RTCPeerConnection.prototype.addTrack;e.RTCPeerConnection.prototype.addStream=function(e){this._localStreams||(this._localStreams=[]),this._localStreams.includes(e)||this._localStreams.push(e),// Try to emulate Chrome's behaviour of adding in audio-video order. +// Safari orders by track id. +e.getAudioTracks().forEach(n=>t.call(this,n,e)),e.getVideoTracks().forEach(n=>t.call(this,n,e))},e.RTCPeerConnection.prototype.addTrack=function(e,...n){return n&&n.forEach(e=>{this._localStreams?this._localStreams.includes(e)||this._localStreams.push(e):this._localStreams=[e]}),t.apply(this,arguments)}}"removeStream"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.removeStream=function(e){this._localStreams||(this._localStreams=[]);let t=this._localStreams.indexOf(e);if(-1===t)return;this._localStreams.splice(t,1);let n=e.getTracks();this.getSenders().forEach(e=>{n.includes(e.track)&&this.removeTrack(e)})})}}function ea(e){if("object"==typeof e&&e.RTCPeerConnection&&("getRemoteStreams"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[]}),!("onaddstream"in e.RTCPeerConnection.prototype))){Object.defineProperty(e.RTCPeerConnection.prototype,"onaddstream",{get(){return this._onaddstream},set(e){this._onaddstream&&(this.removeEventListener("addstream",this._onaddstream),this.removeEventListener("track",this._onaddstreampoly)),this.addEventListener("addstream",this._onaddstream=e),this.addEventListener("track",this._onaddstreampoly=e=>{e.streams.forEach(e=>{if(this._remoteStreams||(this._remoteStreams=[]),this._remoteStreams.includes(e))return;this._remoteStreams.push(e);let t=new Event("addstream");t.stream=e,this.dispatchEvent(t)})})}});let t=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(){let e=this;return this._onaddstreampoly||this.addEventListener("track",this._onaddstreampoly=function(t){t.streams.forEach(t=>{if(e._remoteStreams||(e._remoteStreams=[]),e._remoteStreams.indexOf(t)>=0)return;e._remoteStreams.push(t);let n=new Event("addstream");n.stream=t,e.dispatchEvent(n)})}),t.apply(e,arguments)}}}function ec(e){if("object"!=typeof e||!e.RTCPeerConnection)return;let t=e.RTCPeerConnection.prototype,n=t.createOffer,r=t.createAnswer,i=t.setLocalDescription,o=t.setRemoteDescription,s=t.addIceCandidate;t.createOffer=function(e,t){let r=arguments.length>=2?arguments[2]:arguments[0],i=n.apply(this,[r]);return t?(i.then(e,t),Promise.resolve()):i},t.createAnswer=function(e,t){let n=arguments.length>=2?arguments[2]:arguments[0],i=r.apply(this,[n]);return t?(i.then(e,t),Promise.resolve()):i};let a=function(e,t,n){let r=i.apply(this,[e]);return n?(r.then(t,n),Promise.resolve()):r};t.setLocalDescription=a,a=function(e,t,n){let r=o.apply(this,[e]);return n?(r.then(t,n),Promise.resolve()):r},t.setRemoteDescription=a,a=function(e,t,n){let r=s.apply(this,[e]);return n?(r.then(t,n),Promise.resolve()):r},t.addIceCandidate=a}function el(e){let t=e&&e.navigator;if(t.mediaDevices&&t.mediaDevices.getUserMedia){// shim not needed in Safari 12.1 +let e=t.mediaDevices,n=e.getUserMedia.bind(e);t.mediaDevices.getUserMedia=e=>n(ep(e))}!t.getUserMedia&&t.mediaDevices&&t.mediaDevices.getUserMedia&&(t.getUserMedia=(function(e,n,r){t.mediaDevices.getUserMedia(e).then(n,r)}).bind(t))}function ep(e){return e&&void 0!==e.video?Object.assign({},e,{video:function e(t){return g(t)?Object.keys(t).reduce(function(n,r){let i=g(t[r]),o=i?e(t[r]):t[r],s=i&&!Object.keys(o).length;return void 0===o||s?n:Object.assign(n,{[r]:o})},{}):t}(e.video)}):e}function ed(e){if(!e.RTCPeerConnection)return;// migrate from non-spec RTCIceServer.url to RTCIceServer.urls +let t=e.RTCPeerConnection;e.RTCPeerConnection=function(e,n){if(e&&e.iceServers){let t=[];for(let n=0;nt.generateCertificate})}function eh(e){"object"==typeof e&&e.RTCTrackEvent&&"receiver"in e.RTCTrackEvent.prototype&&!("transceiver"in e.RTCTrackEvent.prototype)&&Object.defineProperty(e.RTCTrackEvent.prototype,"transceiver",{get(){return{receiver:this.receiver}}})}function eu(e){let t=e.RTCPeerConnection.prototype.createOffer;e.RTCPeerConnection.prototype.createOffer=function(e){if(e){void 0!==e.offerToReceiveAudio&&(e.offerToReceiveAudio=!!e.offerToReceiveAudio);let t=this.getTransceivers().find(e=>"audio"===e.receiver.track.kind);!1===e.offerToReceiveAudio&&t?"sendrecv"===t.direction?t.setDirection?t.setDirection("sendonly"):t.direction="sendonly":"recvonly"===t.direction&&(t.setDirection?t.setDirection("inactive"):t.direction="inactive"):!0!==e.offerToReceiveAudio||t||this.addTransceiver("audio",{direction:"recvonly"}),void 0!==e.offerToReceiveVideo&&(e.offerToReceiveVideo=!!e.offerToReceiveVideo);let n=this.getTransceivers().find(e=>"video"===e.receiver.track.kind);!1===e.offerToReceiveVideo&&n?"sendrecv"===n.direction?n.setDirection?n.setDirection("sendonly"):n.direction="sendonly":"recvonly"===n.direction&&(n.setDirection?n.setDirection("inactive"):n.direction="inactive"):!0!==e.offerToReceiveVideo||n||this.addTransceiver("video",{direction:"recvonly"})}return t.apply(this,arguments)}}function ef(e){"object"!=typeof e||e.AudioContext||(e.AudioContext=e.webkitAudioContext)}e(eo,"shimLocalStreamsAPI",()=>es),e(eo,"shimRemoteStreamsAPI",()=>ea),e(eo,"shimCallbacksAPI",()=>ec),e(eo,"shimGetUserMedia",()=>el),e(eo,"shimConstraints",()=>ep),e(eo,"shimRTCIceServerUrls",()=>ed),e(eo,"shimTrackEventTransceiver",()=>eh),e(eo,"shimCreateOfferLegacy",()=>eu),e(eo,"shimAudioContext",()=>ef);var em={};e(em,"shimRTCIceCandidate",()=>e_),e(em,"shimRTCIceCandidateRelayProtocol",()=>eC),e(em,"shimMaxMessageSize",()=>ev),e(em,"shimSendThrowTypeError",()=>eb),e(em,"shimConnectionState",()=>ek),e(em,"removeExtmapAllowMixed",()=>eS),e(em,"shimAddIceCandidateNullOrEmpty",()=>eT),e(em,"shimParameterlessSetLocalDescription",()=>eR);/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + *//* eslint-env node */var eg={};// SDP helpers. +let ey={};function e_(e){// foundation is arbitrarily chosen as an indicator for full support for +// https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface +if(!e.RTCIceCandidate||e.RTCIceCandidate&&"foundation"in e.RTCIceCandidate.prototype)return;let n=e.RTCIceCandidate;e.RTCIceCandidate=function(e){if("object"==typeof e&&e.candidate&&0===e.candidate.indexOf("a=")&&((e=JSON.parse(JSON.stringify(e))).candidate=e.candidate.substring(2)),e.candidate&&e.candidate.length){// Augment the native candidate with the parsed fields. +let r=new n(e),i=/*@__PURE__*/t(eg).parseCandidate(e.candidate);for(let e in i)e in r||Object.defineProperty(r,e,{value:i[e]});return(// Override serializer to not serialize the extra attributes. +r.toJSON=function(){return{candidate:r.candidate,sdpMid:r.sdpMid,sdpMLineIndex:r.sdpMLineIndex,usernameFragment:r.usernameFragment}},r)}return new n(e)},e.RTCIceCandidate.prototype=n.prototype,// Hook up the augmented candidate in onicecandidate and +// addEventListener('icecandidate', ...) +d(e,"icecandidate",t=>(t.candidate&&Object.defineProperty(t,"candidate",{value:new e.RTCIceCandidate(t.candidate),writable:"false"}),t))}function eC(e){!e.RTCIceCandidate||e.RTCIceCandidate&&"relayProtocol"in e.RTCIceCandidate.prototype||// Hook up the augmented candidate in onicecandidate and +// addEventListener('icecandidate', ...) +d(e,"icecandidate",e=>{if(e.candidate){let n=/*@__PURE__*/t(eg).parseCandidate(e.candidate.candidate);"relay"===n.type&&// to relayProtocol. +(e.candidate.relayProtocol=({0:"tls",1:"tcp",2:"udp"})[n.priority>>24])}return e})}function ev(e,n){if(!e.RTCPeerConnection)return;"sctp"in e.RTCPeerConnection.prototype||Object.defineProperty(e.RTCPeerConnection.prototype,"sctp",{get(){return void 0===this._sctp?null:this._sctp}});let r=function(e){if(!e||!e.sdp)return!1;let n=/*@__PURE__*/t(eg).splitSections(e.sdp);return n.shift(),n.some(e=>{let n=/*@__PURE__*/t(eg).parseMLine(e);return n&&"application"===n.kind&&-1!==n.protocol.indexOf("SCTP")})},i=function(e){// TODO: Is there a better solution for detecting Firefox? +let t=e.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);if(null===t||t.length<2)return -1;let n=parseInt(t[1],10);// Test for NaN (yes, this is ugly) +return n!=n?-1:n},o=function(e){// Every implementation we know can send at least 64 KiB. +// Note: Although Chrome is technically able to send up to 256 KiB, the +// data does not reach the other peer reliably. +// See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 +let t=65536;return"firefox"===n.browser&&(// fragmentation. +t=n.version<57?-1===e?16384:2147483637:n.version<60?57===n.version?65535:65536:2147483637),t},s=function(e,r){// Note: 65536 bytes is the default value from the SDP spec. Also, +// every implementation we know supports receiving 65536 bytes. +let i=65536;"firefox"===n.browser&&57===n.version&&(i=65535);let o=/*@__PURE__*/t(eg).matchPrefix(e.sdp,"a=max-message-size:");return o.length>0?i=parseInt(o[0].substring(19),10):"firefox"===n.browser&&-1!==r&&// both local and remote are Firefox, the remote peer can receive +// ~2 GiB. +(i=2147483637),i},a=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(){// Chrome decided to not expose .sctp in plan-b mode. +// As usual, adapter.js has to do an 'ugly worakaround' +// to cover up the mess. +if(this._sctp=null,"chrome"===n.browser&&n.version>=76){let{sdpSemantics:e}=this.getConfiguration();"plan-b"===e&&Object.defineProperty(this,"sctp",{get(){return void 0===this._sctp?null:this._sctp},enumerable:!0,configurable:!0})}if(r(arguments[0])){let e;// Check if the remote is FF. +let t=i(arguments[0]),n=o(t),r=s(arguments[0],t);e=0===n&&0===r?Number.POSITIVE_INFINITY:0===n||0===r?Math.max(n,r):Math.min(n,r);// Create a dummy RTCSctpTransport object and the 'maxMessageSize' +// attribute. +let a={};Object.defineProperty(a,"maxMessageSize",{get:()=>e}),this._sctp=a}return a.apply(this,arguments)}}function eb(e){if(!(e.RTCPeerConnection&&"createDataChannel"in e.RTCPeerConnection.prototype))return;// Note: Although Firefox >= 57 has a native implementation, the maximum +// message size can be reset for all data channels at a later stage. +// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 +function t(e,t){let n=e.send;e.send=function(){let r=arguments[0],i=r.length||r.size||r.byteLength;if("open"===e.readyState&&t.sctp&&i>t.sctp.maxMessageSize)throw TypeError("Message too large (can send a maximum of "+t.sctp.maxMessageSize+" bytes)");return n.apply(e,arguments)}}let n=e.RTCPeerConnection.prototype.createDataChannel;e.RTCPeerConnection.prototype.createDataChannel=function(){let e=n.apply(this,arguments);return t(e,this),e},d(e,"datachannel",e=>(t(e.channel,e.target),e))}function ek(e){if(!e.RTCPeerConnection||"connectionState"in e.RTCPeerConnection.prototype)return;let t=e.RTCPeerConnection.prototype;Object.defineProperty(t,"connectionState",{get(){return({completed:"connected",checking:"connecting"})[this.iceConnectionState]||this.iceConnectionState},enumerable:!0,configurable:!0}),Object.defineProperty(t,"onconnectionstatechange",{get(){return this._onconnectionstatechange||null},set(e){this._onconnectionstatechange&&(this.removeEventListener("connectionstatechange",this._onconnectionstatechange),delete this._onconnectionstatechange),e&&this.addEventListener("connectionstatechange",this._onconnectionstatechange=e)},enumerable:!0,configurable:!0}),["setLocalDescription","setRemoteDescription"].forEach(e=>{let n=t[e];t[e]=function(){return this._connectionstatechangepoly||(this._connectionstatechangepoly=e=>{let t=e.target;if(t._lastConnectionState!==t.connectionState){t._lastConnectionState=t.connectionState;let n=new Event("connectionstatechange",e);t.dispatchEvent(n)}return e},this.addEventListener("iceconnectionstatechange",this._connectionstatechangepoly)),n.apply(this,arguments)}})}function eS(e,t){/* remove a=extmap-allow-mixed for webrtc.org < M71 */if(!e.RTCPeerConnection||"chrome"===t.browser&&t.version>=71||"safari"===t.browser&&t.version>=605)return;let n=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(t){if(t&&t.sdp&&-1!==t.sdp.indexOf("\na=extmap-allow-mixed")){let n=t.sdp.split("\n").filter(e=>"a=extmap-allow-mixed"!==e.trim()).join("\n");e.RTCSessionDescription&&t instanceof e.RTCSessionDescription?arguments[0]=new e.RTCSessionDescription({type:t.type,sdp:n}):t.sdp=n}return n.apply(this,arguments)}}function eT(e,t){// Support for addIceCandidate(null or undefined) +// as well as addIceCandidate({candidate: "", ...}) +// https://bugs.chromium.org/p/chromium/issues/detail?id=978582 +// Note: must be called before other polyfills which change the signature. +if(!(e.RTCPeerConnection&&e.RTCPeerConnection.prototype))return;let n=e.RTCPeerConnection.prototype.addIceCandidate;n&&0!==n.length&&(e.RTCPeerConnection.prototype.addIceCandidate=function(){return arguments[0]?("chrome"===t.browser&&t.version<78||"firefox"===t.browser&&t.version<68||"safari"===t.browser)&&arguments[0]&&""===arguments[0].candidate?Promise.resolve():n.apply(this,arguments):(arguments[1]&&arguments[1].apply(null),Promise.resolve())})}function eR(e,t){if(!(e.RTCPeerConnection&&e.RTCPeerConnection.prototype))return;let n=e.RTCPeerConnection.prototype.setLocalDescription;n&&0!==n.length&&(e.RTCPeerConnection.prototype.setLocalDescription=function(){let e=arguments[0]||{};if("object"!=typeof e||e.type&&e.sdp)return n.apply(this,arguments);if(!// The remaining steps should technically happen when SLD comes off the +// RTCPeerConnection's operations chain (not ahead of going on it), but +// this is too difficult to shim. Instead, this shim only covers the +// common case where the operations chain is empty. This is imperfect, but +// should cover many cases. Rationale: Even if we can't reduce the glare +// window to zero on imperfect implementations, there's value in tapping +// into the perfect negotiation pattern that several browsers support. +(e={type:e.type,sdp:e.sdp}).type)switch(this.signalingState){case"stable":case"have-local-offer":case"have-remote-pranswer":e.type="offer";break;default:e.type="answer"}if(e.sdp||"offer"!==e.type&&"answer"!==e.type)return n.apply(this,[e]);let t="offer"===e.type?this.createOffer:this.createAnswer;return t.apply(this).then(e=>n.apply(this,[e]))})}// Generate an alphanumeric identifier for cname or mids. +// TODO: use UUIDs instead? https://gist.github.com/jed/982883 +ey.generateIdentifier=function(){return Math.random().toString(36).substring(2,12)},// The RTCP CNAME used by all peerconnections from the same JS. +ey.localCName=ey.generateIdentifier(),// Splits SDP into lines, dealing with both CRLF and LF. +ey.splitLines=function(e){return e.trim().split("\n").map(e=>e.trim())},// Splits SDP into sessionpart and mediasections. Ensures CRLF. +ey.splitSections=function(e){let t=e.split("\nm=");return t.map((e,t)=>(t>0?"m="+e:e).trim()+"\r\n")},// Returns the session description. +ey.getDescription=function(e){let t=ey.splitSections(e);return t&&t[0]},// Returns the individual media sections. +ey.getMediaSections=function(e){let t=ey.splitSections(e);return t.shift(),t},// Returns lines that start with a certain prefix. +ey.matchPrefix=function(e,t){return ey.splitLines(e).filter(e=>0===e.indexOf(t))},// Parses an ICE candidate line. Sample input: +// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 +// rport 55996" +// Input can be prefixed with a=. +ey.parseCandidate=function(e){let t;t=0===e.indexOf("a=candidate:")?e.substring(12).split(" "):e.substring(10).split(" ");let n={foundation:t[0],component:{1:"rtp",2:"rtcp"}[t[1]]||t[1],protocol:t[2].toLowerCase(),priority:parseInt(t[3],10),ip:t[4],address:t[4],port:parseInt(t[5],10),// skip parts[6] == 'typ' +type:t[7]};for(let e=8;e0?t[0].split("/")[1]:"sendrecv",uri:t[1],attributes:t.slice(2).join(" ")}},// Generates an extmap line from RTCRtpHeaderExtensionParameters or +// RTCRtpHeaderExtension. +ey.writeExtmap=function(e){return"a=extmap:"+(e.id||e.preferredId)+(e.direction&&"sendrecv"!==e.direction?"/"+e.direction:"")+" "+e.uri+(e.attributes?" "+e.attributes:"")+"\r\n"},// Parses a fmtp line, returns dictionary. Sample input: +// a=fmtp:96 vbr=on;cng=on +// Also deals with vbr=on; cng=on +ey.parseFmtp=function(e){let t;let n={},r=e.substring(e.indexOf(" ")+1).split(";");for(let e=0;e{void 0!==e.parameters[t]?r.push(t+"="+e.parameters[t]):r.push(t)}),t+="a=fmtp:"+n+" "+r.join(";")+"\r\n"}return t},// Parses a rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// a=rtcp-fb:98 nack rpsi +ey.parseRtcpFb=function(e){let t=e.substring(e.indexOf(" ")+1).split(" ");return{type:t.shift(),parameter:t.join(" ")}},// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. +ey.writeRtcpFb=function(e){let t="",n=e.payloadType;return void 0!==e.preferredPayloadType&&(n=e.preferredPayloadType),e.rtcpFeedback&&e.rtcpFeedback.length&&e.rtcpFeedback.forEach(e=>{t+="a=rtcp-fb:"+n+" "+e.type+(e.parameter&&e.parameter.length?" "+e.parameter:"")+"\r\n"}),t},// Parses a RFC 5576 ssrc media attribute. Sample input: +// a=ssrc:3735928559 cname:something +ey.parseSsrcMedia=function(e){let t=e.indexOf(" "),n={ssrc:parseInt(e.substring(7,t),10)},r=e.indexOf(":",t);return r>-1?(n.attribute=e.substring(t+1,r),n.value=e.substring(r+1)):n.attribute=e.substring(t+1),n},// Parse a ssrc-group line (see RFC 5576). Sample input: +// a=ssrc-group:semantics 12 34 +ey.parseSsrcGroup=function(e){let t=e.substring(13).split(" ");return{semantics:t.shift(),ssrcs:t.map(e=>parseInt(e,10))}},// Extracts the MID (RFC 5888) from a media section. +// Returns the MID or undefined if no mid line was found. +ey.getMid=function(e){let t=ey.matchPrefix(e,"a=mid:")[0];if(t)return t.substring(6)},// Parses a fingerprint line for DTLS-SRTP. +ey.parseFingerprint=function(e){let t=e.substring(14).split(" ");return{algorithm:t[0].toLowerCase(),value:t[1].toUpperCase()}},// Extracts DTLS parameters from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the fingerprint line as input. See also getIceParameters. +ey.getDtlsParameters=function(e,t){let n=ey.matchPrefix(e+t,"a=fingerprint:");// Note: a=setup line is ignored since we use the 'auto' role in Edge. +return{role:"auto",fingerprints:n.map(ey.parseFingerprint)}},// Serializes DTLS parameters to SDP. +ey.writeDtlsParameters=function(e,t){let n="a=setup:"+t+"\r\n";return e.fingerprints.forEach(e=>{n+="a=fingerprint:"+e.algorithm+" "+e.value+"\r\n"}),n},// Parses a=crypto lines into +// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members +ey.parseCryptoLine=function(e){let t=e.substring(9).split(" ");return{tag:parseInt(t[0],10),cryptoSuite:t[1],keyParams:t[2],sessionParams:t.slice(3)}},ey.writeCryptoLine=function(e){return"a=crypto:"+e.tag+" "+e.cryptoSuite+" "+("object"==typeof e.keyParams?ey.writeCryptoKeyParams(e.keyParams):e.keyParams)+(e.sessionParams?" "+e.sessionParams.join(" "):"")+"\r\n"},// Parses the crypto key parameters into +// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* +ey.parseCryptoKeyParams=function(e){if(0!==e.indexOf("inline:"))return null;let t=e.substring(7).split("|");return{keyMethod:"inline",keySalt:t[0],lifeTime:t[1],mkiValue:t[2]?t[2].split(":")[0]:void 0,mkiLength:t[2]?t[2].split(":")[1]:void 0}},ey.writeCryptoKeyParams=function(e){return e.keyMethod+":"+e.keySalt+(e.lifeTime?"|"+e.lifeTime:"")+(e.mkiValue&&e.mkiLength?"|"+e.mkiValue+":"+e.mkiLength:"")},// Extracts all SDES parameters. +ey.getCryptoParameters=function(e,t){let n=ey.matchPrefix(e+t,"a=crypto:");return n.map(ey.parseCryptoLine)},// Parses ICE information from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the ice-ufrag and ice-pwd lines as input. +ey.getIceParameters=function(e,t){let n=ey.matchPrefix(e+t,"a=ice-ufrag:")[0],r=ey.matchPrefix(e+t,"a=ice-pwd:")[0];return n&&r?{usernameFragment:n.substring(12),password:r.substring(10)}:null},// Serializes ICE parameters to SDP. +ey.writeIceParameters=function(e){let t="a=ice-ufrag:"+e.usernameFragment+"\r\na=ice-pwd:"+e.password+"\r\n";return e.iceLite&&(t+="a=ice-lite\r\n"),t},// Parses the SDP media section and returns RTCRtpParameters. +ey.parseRtpParameters=function(e){let t={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]},n=ey.splitLines(e),r=n[0].split(" ");t.profile=r[2];for(let n=3;n is considered. +n.parameters=r.length?ey.parseFmtp(r[0]):{},n.rtcpFeedback=ey.matchPrefix(e,"a=rtcp-fb:"+i+" ").map(ey.parseRtcpFb),t.codecs.push(n),n.name.toUpperCase()){case"RED":case"ULPFEC":t.fecMechanisms.push(n.name.toUpperCase())}}}ey.matchPrefix(e,"a=extmap:").forEach(e=>{t.headerExtensions.push(ey.parseExtmap(e))});let i=ey.matchPrefix(e,"a=rtcp-fb:* ").map(ey.parseRtcpFb);// FIXME: parse rtcp. +return t.codecs.forEach(e=>{i.forEach(t=>{let n=e.rtcpFeedback.find(e=>e.type===t.type&&e.parameter===t.parameter);n||e.rtcpFeedback.push(t)})}),t},// Generates parts of the SDP media section describing the capabilities / +// parameters. +ey.writeRtpDescription=function(e,t){let n="";n+="m="+e+" "+(t.codecs.length>0?"9":"0")+" "+(t.profile||"UDP/TLS/RTP/SAVPF")+" "+t.codecs.map(e=>void 0!==e.preferredPayloadType?e.preferredPayloadType:e.payloadType).join(" ")+"\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\n",// Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. +t.codecs.forEach(e=>{n+=ey.writeRtpMap(e)+ey.writeFmtp(e)+ey.writeRtcpFb(e)});let r=0;// FIXME: write fecMechanisms. +return t.codecs.forEach(e=>{e.maxptime>r&&(r=e.maxptime)}),r>0&&(n+="a=maxptime:"+r+"\r\n"),t.headerExtensions&&t.headerExtensions.forEach(e=>{n+=ey.writeExtmap(e)}),n},// Parses the SDP media section and returns an array of +// RTCRtpEncodingParameters. +ey.parseRtpEncodingParameters=function(e){let t;let n=[],r=ey.parseRtpParameters(e),i=-1!==r.fecMechanisms.indexOf("RED"),o=-1!==r.fecMechanisms.indexOf("ULPFEC"),s=ey.matchPrefix(e,"a=ssrc:").map(e=>ey.parseSsrcMedia(e)).filter(e=>"cname"===e.attribute),a=s.length>0&&s[0].ssrc,c=ey.matchPrefix(e,"a=ssrc-group:FID").map(e=>{let t=e.substring(17).split(" ");return t.map(e=>parseInt(e,10))});c.length>0&&c[0].length>1&&c[0][0]===a&&(t=c[0][1]),r.codecs.forEach(e=>{if("RTX"===e.name.toUpperCase()&&e.parameters.apt){let r={ssrc:a,codecPayloadType:parseInt(e.parameters.apt,10)};a&&t&&(r.rtx={ssrc:t}),n.push(r),i&&((r=JSON.parse(JSON.stringify(r))).fec={ssrc:a,mechanism:o?"red+ulpfec":"red"},n.push(r))}}),0===n.length&&a&&n.push({ssrc:a});// we support both b=AS and b=TIAS but interpret AS as TIAS. +let l=ey.matchPrefix(e,"b=");return l.length&&(l=0===l[0].indexOf("b=TIAS:")?parseInt(l[0].substring(7),10):0===l[0].indexOf("b=AS:")?950*parseInt(l[0].substring(5),10)-16e3:void 0,n.forEach(e=>{e.maxBitrate=l})),n},// parses http://draft.ortc.org/#rtcrtcpparameters* +ey.parseRtcpParameters=function(e){let t={},n=ey.matchPrefix(e,"a=ssrc:").map(e=>ey.parseSsrcMedia(e)).filter(e=>"cname"===e.attribute)[0];n&&(t.cname=n.value,t.ssrc=n.ssrc);// Edge uses the compound attribute instead of reducedSize +// compound is !reducedSize +let r=ey.matchPrefix(e,"a=rtcp-rsize");t.reducedSize=r.length>0,t.compound=0===r.length;// parses the rtcp-mux attrіbute. +// Note that Edge does not support unmuxed RTCP. +let i=ey.matchPrefix(e,"a=rtcp-mux");return t.mux=i.length>0,t},ey.writeRtcpParameters=function(e){let t="";return e.reducedSize&&(t+="a=rtcp-rsize\r\n"),e.mux&&(t+="a=rtcp-mux\r\n"),void 0!==e.ssrc&&e.cname&&(t+="a=ssrc:"+e.ssrc+" cname:"+e.cname+"\r\n"),t},// parses either a=msid: or a=ssrc:... msid lines and returns +// the id of the MediaStream and MediaStreamTrack. +ey.parseMsid=function(e){let t;let n=ey.matchPrefix(e,"a=msid:");if(1===n.length)return{stream:(t=n[0].substring(7).split(" "))[0],track:t[1]};let r=ey.matchPrefix(e,"a=ssrc:").map(e=>ey.parseSsrcMedia(e)).filter(e=>"msid"===e.attribute);if(r.length>0)return{stream:(t=r[0].value.split(" "))[0],track:t[1]}},// SCTP +// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back +// to draft-ietf-mmusic-sctp-sdp-05 +ey.parseSctpDescription=function(e){let t;let n=ey.parseMLine(e),r=ey.matchPrefix(e,"a=max-message-size:");r.length>0&&(t=parseInt(r[0].substring(19),10)),isNaN(t)&&(t=65536);let i=ey.matchPrefix(e,"a=sctp-port:");if(i.length>0)return{port:parseInt(i[0].substring(12),10),protocol:n.fmt,maxMessageSize:t};let o=ey.matchPrefix(e,"a=sctpmap:");if(o.length>0){let e=o[0].substring(10).split(" ");return{port:parseInt(e[0],10),protocol:e[1],maxMessageSize:t}}},// SCTP +// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers +// support by now receiving in this format, unless we originally parsed +// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line +// protocol of DTLS/SCTP -- without UDP/ or TCP/) +ey.writeSctpDescription=function(e,t){let n=[];return n="DTLS/SCTP"!==e.protocol?["m="+e.kind+" 9 "+e.protocol+" "+t.protocol+"\r\n","c=IN IP4 0.0.0.0\r\n","a=sctp-port:"+t.port+"\r\n"]:["m="+e.kind+" 9 "+e.protocol+" "+t.port+"\r\n","c=IN IP4 0.0.0.0\r\n","a=sctpmap:"+t.port+" "+t.protocol+" 65535\r\n"],void 0!==t.maxMessageSize&&n.push("a=max-message-size:"+t.maxMessageSize+"\r\n"),n.join("")},// Generate a session ID for SDP. +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 +// recommends using a cryptographically random +ve 64-bit value +// but right now this should be acceptable and within the right range +ey.generateSessionId=function(){return Math.random().toString().substr(2,22)},// Write boiler plate for start of SDP +// sessId argument is optional - if not supplied it will +// be generated randomly +// sessVersion is optional and defaults to 2 +// sessUser is optional and defaults to 'thisisadapterortc' +ey.writeSessionBoilerplate=function(e,t,n){return"v=0\r\no="+(n||"thisisadapterortc")+" "+(e||ey.generateSessionId())+" "+(void 0!==t?t:2)+" IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"},// Gets the direction from the mediaSection or the sessionpart. +ey.getDirection=function(e,t){// Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. +let n=ey.splitLines(e);for(let e=0;e=this.minChromeVersion:"firefox"===e?t>=this.minFirefoxVersion:"safari"===e&&!this.isIOS&&t>=this.minSafariVersion)}getBrowser(){return eP.browserDetails.browser}getVersion(){return eP.browserDetails.version||0}isUnifiedPlanSupported(){let e;let t=this.getBrowser(),n=eP.browserDetails.version||0;if("chrome"===t&&n=this.minFirefoxVersion)return!0;if(!window.RTCRtpTransceiver||!("currentDirection"in RTCRtpTransceiver.prototype))return!1;let r=!1;try{(e=new RTCPeerConnection).addTransceiver("audio"),r=!0}catch(e){}finally{e&&e.close()}return r}toString(){return`Supports: + browser:${this.getBrowser()} + version:${this.getVersion()} + isIOS:${this.isIOS} + isWebRTCSupported:${this.isWebRTCSupported()} + isBrowserSupported:${this.isBrowserSupported()} + isUnifiedPlanSupported:${this.isUnifiedPlanSupported()}`}constructor(){this.isIOS=["iPad","iPhone","iPod"].includes(navigator.platform),this.supportedBrowsers=["firefox","chrome","safari"],this.minFirefoxVersion=59,this.minChromeVersion=72,this.minSafariVersion=605}},eD=e=>!e||/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.test(e),ex=()=>Math.random().toString(36).slice(2),eI={iceServers:[{urls:"stun:stun.l.google.com:19302"},{urls:["turn:eu-0.turn.peerjs.com:3478","turn:us-0.turn.peerjs.com:3478"],username:"peerjs",credential:"peerjsp"}],sdpSemantics:"unified-plan"},eM=new class extends n{noop(){}blobToArrayBuffer(e,t){let n=new FileReader;return n.onload=function(e){e.target&&t(e.target.result)},n.readAsArrayBuffer(e),n}binaryStringToArrayBuffer(e){let t=new Uint8Array(e.length);for(let n=0;n=w.All&&this._print(w.All,...e)}warn(...e){this._logLevel>=w.Warnings&&this._print(w.Warnings,...e)}error(...e){this._logLevel>=w.Errors&&this._print(w.Errors,...e)}setLogFunction(e){this._print=e}_print(e,...t){let n=["PeerJS: ",...t];for(let e in n)n[e]instanceof Error&&(n[e]="("+n[e].name+") "+n[e].message);e>=w.All?console.log(...n):e>=w.Warnings?console.warn("WARNING",...n):e>=w.Errors&&console.error("ERROR",...n)}constructor(){this._logLevel=w.Disabled}},ej={},eL=Object.prototype.hasOwnProperty,eA="~";/** + * Constructor to create a storage for our `EE` objects. + * An `Events` instance is a plain object whose properties are event names. + * + * @constructor + * @private + */function eB(){}/** + * Representation of a single event listener. + * + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} [once=false] Specify if the listener is a one-time listener. + * @constructor + * @private + */function eF(e,t,n){this.fn=e,this.context=t,this.once=n||!1}/** + * Add a listener for a given event. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} once Specify if the listener is a one-time listener. + * @returns {EventEmitter} + * @private + */function ez(e,t,n,r,i){if("function"!=typeof n)throw TypeError("The listener must be a function");var o=new eF(n,r||e,i),s=eA?eA+t:t;return e._events[s]?e._events[s].fn?e._events[s]=[e._events[s],o]:e._events[s].push(o):(e._events[s]=o,e._eventsCount++),e}/** + * Clear event by name. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} evt The Event name. + * @private + */function eU(e,t){0==--e._eventsCount?e._events=new eB:delete e._events[t]}/** + * Minimal `EventEmitter` interface that is molded against the Node.js + * `EventEmitter` interface. + * + * @constructor + * @public + */function eN(){this._events=new eB,this._eventsCount=0}Object.create&&(eB.prototype=Object.create(null),new eB().__proto__||(eA=!1)),/** + * Return an array listing the events for which the emitter has registered + * listeners. + * + * @returns {Array} + * @public + */eN.prototype.eventNames=function(){var e,t,n=[];if(0===this._eventsCount)return n;for(t in e=this._events)eL.call(e,t)&&n.push(eA?t.slice(1):t);return Object.getOwnPropertySymbols?n.concat(Object.getOwnPropertySymbols(e)):n},/** + * Return the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Array} The registered listeners. + * @public + */eN.prototype.listeners=function(e){var t=eA?eA+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var r=0,i=n.length,o=Array(i);r","afrokick ","ericz ","Jairo ","Jonas Gloning <34194370+jonasgloning@users.noreply.github.com>","Jairo Caro-Accino Viciana ","Carlos Caballero ","hc ","Muhammad Asif ","PrashoonB ","Harsh Bardhan Mishra <47351025+HarshCasper@users.noreply.github.com>","akotynski ","lmb ","Jairooo ","Moritz St\xfcckler ","Simon ","Denis Lukov ","Philipp Hancke ","Hans Oksendahl ","Jess ","khankuan ","DUODVK ","XiZhao ","Matthias Lohr ","=frank tree <=frnktrb@googlemail.com>","Andre Eckardt ","Chris Cowan ","Alex Chuev ","alxnull ","Yemel Jardi ","Ben Parnell ","Benny Lichtner ","fresheneesz ","bob.barstead@exaptive.com ","chandika ","emersion ","Christopher Van ","eddieherm ","Eduardo Pinho ","Evandro Zanatta ","Gardner Bickford ","Gian Luca ","PatrickJS ","jonnyf ","Hizkia Felix ","Hristo Oskov ","Isaac Madwed ","Ilya Konanykhin ","jasonbarry ","Jonathan Burke ","Josh Hamit ","Jordan Austin ","Joel Wetzell ","xizhao ","Alberto Torres ","Jonathan Mayol ","Jefferson Felix ","Rolf Erik Lekang ","Kevin Mai-Husan Chia ","Pepijn de Vos ","JooYoung ","Tobias Speicher ","Steve Blaurock ","Kyrylo Shegeda ","Diwank Singh Tomer ","Sören Balko ","Arpit Solanki ","Yuki Ito ","Artur Zayats "],"funding":{"type":"opencollective","url":"https://opencollective.com/peer"},"collective":{"type":"opencollective","url":"https://opencollective.com/peer"},"files":["dist/*"],"sideEffects":["lib/global.ts","lib/supports.ts"],"main":"dist/bundler.cjs","module":"dist/bundler.mjs","browser-minified":"dist/peerjs.min.js","browser-unminified":"dist/peerjs.js","browser-minified-cbor":"dist/serializer.cbor.mjs","browser-minified-msgpack":"dist/serializer.msgpack.mjs","types":"dist/types.d.ts","engines":{"node":">= 14"},"targets":{"types":{"source":"lib/exports.ts"},"main":{"source":"lib/exports.ts","sourceMap":{"inlineSources":true}},"module":{"source":"lib/exports.ts","includeNodeModules":["eventemitter3"],"sourceMap":{"inlineSources":true}},"browser-minified":{"context":"browser","outputFormat":"global","optimize":true,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 80, safari >= 15"},"source":"lib/global.ts"},"browser-unminified":{"context":"browser","outputFormat":"global","optimize":false,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 80, safari >= 15"},"source":"lib/global.ts"},"browser-minified-cbor":{"context":"browser","outputFormat":"esmodule","isLibrary":true,"optimize":true,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 102, safari >= 15"},"source":"lib/dataconnection/StreamConnection/Cbor.ts"},"browser-minified-msgpack":{"context":"browser","outputFormat":"esmodule","isLibrary":true,"optimize":true,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 102, safari >= 15"},"source":"lib/dataconnection/StreamConnection/MsgPack.ts"}},"scripts":{"contributors":"git-authors-cli --print=false && prettier --write package.json && git add package.json package-lock.json && git commit -m \\"chore(contributors): update and sort contributors list\\"","check":"tsc --noEmit && tsc -p e2e/tsconfig.json --noEmit","watch":"parcel watch","build":"rm -rf dist && parcel build","prepublishOnly":"npm run build","test":"jest","test:watch":"jest --watch","coverage":"jest --coverage --collectCoverageFrom=\\"./lib/**\\"","format":"prettier --write .","format:check":"prettier --check .","semantic-release":"semantic-release","e2e":"wdio run e2e/wdio.local.conf.ts","e2e:bstack":"wdio run e2e/wdio.bstack.conf.ts"},"devDependencies":{"@parcel/config-default":"^2.9.3","@parcel/packager-ts":"^2.9.3","@parcel/transformer-typescript-tsc":"^2.9.3","@parcel/transformer-typescript-types":"^2.9.3","@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@swc/core":"^1.3.27","@swc/jest":"^0.2.24","@types/jasmine":"^4.3.4","@wdio/browserstack-service":"^8.11.2","@wdio/cli":"^8.11.2","@wdio/globals":"^8.11.2","@wdio/jasmine-framework":"^8.11.2","@wdio/local-runner":"^8.11.2","@wdio/spec-reporter":"^8.11.2","@wdio/types":"^8.10.4","http-server":"^14.1.1","jest":"^29.3.1","jest-environment-jsdom":"^29.3.1","mock-socket":"^9.0.0","parcel":"^2.9.3","prettier":"^3.0.0","semantic-release":"^21.0.0","ts-node":"^10.9.1","typescript":"^5.0.0","wdio-geckodriver-service":"^5.0.1"},"dependencies":{"@msgpack/msgpack":"^2.8.0","cbor-x":"^1.5.3","eventemitter3":"^4.0.7","peerjs-js-binarypack":"^2.0.0","webrtc-adapter":"^8.0.0"},"alias":{"process":false,"buffer":false}}');class eJ extends ej.EventEmitter{start(e,t){this._id=e;let n=`${this._baseUrl}&id=${e}&token=${t}`;!this._socket&&this._disconnected&&(this._socket=new WebSocket(n+"&version="+e$.version),this._disconnected=!1,this._socket.onmessage=e=>{let t;try{t=JSON.parse(e.data),eO.log("Server message received:",t)}catch(t){eO.log("Invalid server message",e.data);return}this.emit(M.Message,t)},this._socket.onclose=e=>{this._disconnected||(eO.log("Socket closed.",e),this._cleanup(),this._disconnected=!0,this.emit(M.Disconnected))},// Take care of the queue of connections if necessary and make sure Peer knows +// socket is open. +this._socket.onopen=()=>{this._disconnected||(this._sendQueuedMessages(),eO.log("Socket open"),this._scheduleHeartbeat())})}_scheduleHeartbeat(){this._wsPingTimer=setTimeout(()=>{this._sendHeartbeat()},this.pingInterval)}_sendHeartbeat(){if(!this._wsOpen()){eO.log("Cannot send heartbeat, because socket closed");return}let e=JSON.stringify({type:O.Heartbeat});this._socket.send(e),this._scheduleHeartbeat()}/** Is the websocket currently open? */_wsOpen(){return!!this._socket&&1===this._socket.readyState}/** Send queued messages. */_sendQueuedMessages(){//Create copy of queue and clear it, +//because send method push the message back to queue if smth will go wrong +let e=[...this._messagesQueue];for(let t of(this._messagesQueue=[],e))this.send(t)}/** Exposed send for DC & Peer. */send(e){if(this._disconnected)return;// If we didn't get an ID yet, we can't yet send anything so we should queue +// up these messages. +if(!this._id){this._messagesQueue.push(e);return}if(!e.type){this.emit(M.Error,"Invalid message");return}if(!this._wsOpen())return;let t=JSON.stringify(e);this._socket.send(t)}close(){this._disconnected||(this._cleanup(),this._disconnected=!0)}_cleanup(){this._socket&&(this._socket.onopen=this._socket.onmessage=this._socket.onclose=null,this._socket.close(),this._socket=void 0),clearTimeout(this._wsPingTimer)}constructor(e,t,n,r,i,o=5e3){super(),this.pingInterval=o,this._disconnected=!0,this._messagesQueue=[],this._baseUrl=(e?"wss://":"ws://")+t+":"+n+r+"peerjs?key="+i}}class eV{/** Returns a PeerConnection object set up correctly (for data, media). */startConnection(e){let t=this._startPeerConnection();// What do we need to do now? +if(// Set the connection's PC. +this.connection.peerConnection=t,this.connection.type===P.Media&&e._stream&&this._addTracksToConnection(e._stream,t),e.originator){let n=this.connection,r={ordered:!!e.reliable},i=t.createDataChannel(n.label,r);n._initializeDataChannel(i),this._makeOffer()}else this.handleSDP("OFFER",e.sdp)}/** Start a PC. */_startPeerConnection(){eO.log("Creating RTCPeerConnection.");let e=new RTCPeerConnection(this.connection.provider.options.config);return this._setupListeners(e),e}/** Set up various WebRTC listeners. */_setupListeners(e){let t=this.connection.peer,n=this.connection.connectionId,r=this.connection.type,i=this.connection.provider;eO.log("Listening for ICE candidates."),e.onicecandidate=e=>{e.candidate&&e.candidate.candidate&&(eO.log(`Received ICE candidates for ${t}:`,e.candidate),i.socket.send({type:O.Candidate,payload:{candidate:e.candidate,type:r,connectionId:n},dst:t}))},e.oniceconnectionstatechange=()=>{switch(e.iceConnectionState){case"failed":eO.log("iceConnectionState is failed, closing connections to "+t),this.connection.emitError(D.NegotiationFailed,"Negotiation of connection to "+t+" failed."),this.connection.close();break;case"closed":eO.log("iceConnectionState is closed, closing connections to "+t),this.connection.emitError(D.ConnectionClosed,"Connection to "+t+" closed."),this.connection.close();break;case"disconnected":eO.log("iceConnectionState changed to disconnected on the connection with "+t);break;case"completed":e.onicecandidate=()=>{}}this.connection.emit("iceStateChanged",e.iceConnectionState)},eO.log("Listening for data channel"),// Fired between offer and answer, so options should already be saved +// in the options hash. +e.ondatachannel=e=>{eO.log("Received data channel");let r=e.channel,o=i.getConnection(t,n);o._initializeDataChannel(r)},eO.log("Listening for remote stream"),e.ontrack=e=>{eO.log("Received remote stream");let r=e.streams[0],o=i.getConnection(t,n);o.type===P.Media&&this._addStreamToMediaConnection(r,o)}}cleanup(){eO.log("Cleaning up PeerConnection to "+this.connection.peer);let e=this.connection.peerConnection;if(!e)return;this.connection.peerConnection=null,//unsubscribe from all PeerConnection's events +e.onicecandidate=e.oniceconnectionstatechange=e.ondatachannel=e.ontrack=()=>{};let t="closed"!==e.signalingState,n=!1,r=this.connection.dataChannel;r&&(n=!!r.readyState&&"closed"!==r.readyState),(t||n)&&e.close()}async _makeOffer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createOffer(this.connection.options.constraints);eO.log("Created offer."),this.connection.options.sdpTransform&&"function"==typeof this.connection.options.sdpTransform&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp);try{await e.setLocalDescription(n),eO.log("Set localDescription:",n,`for:${this.connection.peer}`);let r={sdp:n,type:this.connection.type,connectionId:this.connection.connectionId,metadata:this.connection.metadata};if(this.connection.type===P.Data){let e=this.connection;r={...r,label:e.label,reliable:e.reliable,serialization:e.serialization}}t.socket.send({type:O.Offer,payload:r,dst:this.connection.peer})}catch(e){"OperationError: Failed to set local offer sdp: Called in wrong state: kHaveRemoteOffer"!=e&&(t.emitError(E.WebRTC,e),eO.log("Failed to setLocalDescription, ",e))}}catch(e){t.emitError(E.WebRTC,e),eO.log("Failed to createOffer, ",e)}}async _makeAnswer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createAnswer();eO.log("Created answer."),this.connection.options.sdpTransform&&"function"==typeof this.connection.options.sdpTransform&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp);try{await e.setLocalDescription(n),eO.log("Set localDescription:",n,`for:${this.connection.peer}`),t.socket.send({type:O.Answer,payload:{sdp:n,type:this.connection.type,connectionId:this.connection.connectionId},dst:this.connection.peer})}catch(e){t.emitError(E.WebRTC,e),eO.log("Failed to setLocalDescription, ",e)}}catch(e){t.emitError(E.WebRTC,e),eO.log("Failed to create answer, ",e)}}/** Handle an SDP. */async handleSDP(e,t){t=new RTCSessionDescription(t);let n=this.connection.peerConnection,r=this.connection.provider;eO.log("Setting remote description",t);try{await n.setRemoteDescription(t),eO.log(`Set remoteDescription:${e} for:${this.connection.peer}`),"OFFER"===e&&await this._makeAnswer()}catch(e){r.emitError(E.WebRTC,e),eO.log("Failed to setRemoteDescription, ",e)}}/** Handle a candidate. */async handleCandidate(e){eO.log("handleCandidate:",e);try{await this.connection.peerConnection.addIceCandidate(e),eO.log(`Added ICE candidate for:${this.connection.peer}`)}catch(e){this.connection.provider.emitError(E.WebRTC,e),eO.log("Failed to handleCandidate, ",e)}}_addTracksToConnection(e,t){if(eO.log(`add tracks from stream ${e.id} to peer connection`),!t.addTrack)return eO.error("Your browser does't support RTCPeerConnection#addTrack. Ignored.");e.getTracks().forEach(n=>{t.addTrack(n,e)})}_addStreamToMediaConnection(e,t){eO.log(`add stream ${e.id} to media connection ${t.connectionId}`),t.addStream(e)}constructor(e){this.connection=e}}class eG extends ej.EventEmitter{/** + * Emits a typed error message. + * + * @internal + */emitError(e,t){eO.error("Error:",t),// @ts-ignore +this.emit("error",new eW(`${e}`,t))}}class eW extends Error{/** + * @internal + */constructor(e,t){"string"==typeof t?super(t):(super(),Object.assign(this,t)),this.type=e}}class eH extends eG{/** + * Whether the media connection is active (e.g. your call has been answered). + * You can check this if you want to set a maximum wait time for a one-sided call. + */get open(){return this._open}constructor(e,t,n){super(),this.peer=e,this.provider=t,this.options=n,this._open=!1,this.metadata=n.metadata}}class eY extends eH{/** + * For media connections, this is always 'media'. + */get type(){return P.Media}get localStream(){return this._localStream}get remoteStream(){return this._remoteStream}/** Called by the Negotiator when the DataChannel is ready. */_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{eO.log(`DC#${this.connectionId} dc connection success`),this.emit("willCloseOnRemote")},this.dataChannel.onclose=()=>{eO.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}addStream(e){eO.log("Receiving stream",e),this._remoteStream=e,super.emit("stream",e)}/** + * @internal + */handleMessage(e){let t=e.type,n=e.payload;switch(e.type){case O.Answer:this._negotiator.handleSDP(t,n.sdp),this._open=!0;break;case O.Candidate:this._negotiator.handleCandidate(n.candidate);break;default:eO.warn(`Unrecognized message type:${t} from peer:${this.peer}`)}}/** + * When receiving a {@apilink PeerEvents | `call`} event on a peer, you can call + * `answer` on the media connection provided by the callback to accept the call + * and optionally send your own media stream. + + * + * @param stream A WebRTC media stream. + * @param options + * @returns + */answer(e,t={}){if(this._localStream){eO.warn("Local stream already exists on this MediaConnection. Are you answering a call twice?");return}this._localStream=e,t&&t.sdpTransform&&(this.options.sdpTransform=t.sdpTransform),this._negotiator.startConnection({...this.options._payload,_stream:e});// Retrieve lost messages stored because PeerConnection not set up. +let n=this.provider._getMessages(this.connectionId);for(let e of n)this.handleMessage(e);this._open=!0}/** + * Exposed functionality for users. + *//** + * Closes the media connection. + */close(){this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this._localStream=null,this._remoteStream=null,this.provider&&(this.provider._removeConnection(this),this.provider=null),this.options&&this.options._stream&&(this.options._stream=null),this.open&&(this._open=!1,super.emit("close"))}constructor(e,t,n){super(e,t,n),this._localStream=this.options._stream,this.connectionId=this.options.connectionId||eY.ID_PREFIX+eM.randomToken(),this._negotiator=new eV(this),this._localStream&&this._negotiator.startConnection({_stream:this._localStream,originator:!0})}}eY.ID_PREFIX="mc_";class eK{_buildRequest(e){let t=this._options.secure?"https":"http",{host:n,port:r,path:i,key:o}=this._options,s=new URL(`${t}://${n}:${r}${i}${o}/${e}`);return(// TODO: Why timestamp, why random? +s.searchParams.set("ts",`${Date.now()}${Math.random()}`),s.searchParams.set("version",e$.version),fetch(s.href,{referrerPolicy:this._options.referrerPolicy}))}/** Get a unique ID from the server via XHR and initialize with it. */async retrieveId(){try{let e=await this._buildRequest("id");if(200!==e.status)throw Error(`Error. Status:${e.status}`);return e.text()}catch(t){eO.error("Error retrieving ID",t);let e="";throw"/"===this._options.path&&this._options.host!==eM.CLOUD_HOST&&(e=" If you passed in a `path` to your self-hosted PeerServer, you'll also need to pass in that same path when creating a new Peer."),Error("Could not get an ID from the server."+e)}}/** @deprecated */async listAllPeers(){try{let e=await this._buildRequest("peers");if(200!==e.status){if(401===e.status){let e="";throw e=this._options.host===eM.CLOUD_HOST?"It looks like you're using the cloud server. You can email team@peerjs.com to enable peer listing for your API key.":"You need to enable `allow_discovery` on your self-hosted PeerServer to use this feature.",Error("It doesn't look like you have permission to list peers IDs. "+e)}throw Error(`Error. Status:${e.status}`)}return e.json()}catch(e){throw eO.error("Error retrieving list peers",e),Error("Could not get list peers from the server."+e)}}constructor(e){this._options=e}}class eX extends eH{get type(){return P.Data}/** Called by the Negotiator when the DataChannel is ready. */_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{eO.log(`DC#${this.connectionId} dc connection success`),this._open=!0,this.emit("open")},this.dataChannel.onmessage=e=>{eO.log(`DC#${this.connectionId} dc onmessage:`,e.data);// this._handleDataMessage(e); +},this.dataChannel.onclose=()=>{eO.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}/** + * Exposed functionality for users. + *//** Allows user to close connection. */close(e){if(e?.flush){this.send({__peerData:{type:"close"}});return}this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this.provider&&(this.provider._removeConnection(this),this.provider=null),this.dataChannel&&(this.dataChannel.onopen=null,this.dataChannel.onmessage=null,this.dataChannel.onclose=null,this.dataChannel=null),this.open&&(this._open=!1,super.emit("close"))}/** Allows user to send data. */send(e,t=!1){if(!this.open){this.emitError(x.NotOpenYet,"Connection is not open. You should listen for the `open` event before sending messages.");return}return this._send(e,t)}async handleMessage(e){let t=e.payload;switch(e.type){case O.Answer:await this._negotiator.handleSDP(e.type,t.sdp);break;case O.Candidate:await this._negotiator.handleCandidate(t.candidate);break;default:eO.warn("Unrecognized message type:",e.type,"from peer:",this.peer)}}constructor(e,t,n){super(e,t,n),this.connectionId=this.options.connectionId||eX.ID_PREFIX+ex(),this.label=this.options.label||this.connectionId,this.reliable=!!this.options.reliable,this._negotiator=new eV(this),this._negotiator.startConnection(this.options._payload||{originator:!0,reliable:this.reliable})}}eX.ID_PREFIX="dc_",eX.MAX_BUFFERED_AMOUNT=8388608;class eq extends eX{get bufferSize(){return this._bufferSize}_initializeDataChannel(e){super._initializeDataChannel(e),this.dataChannel.binaryType="arraybuffer",this.dataChannel.addEventListener("message",e=>this._handleDataMessage(e))}_bufferedSend(e){(this._buffering||!this._trySend(e))&&(this._buffer.push(e),this._bufferSize=this._buffer.length)}// Returns true if the send succeeds. +_trySend(e){if(!this.open)return!1;if(this.dataChannel.bufferedAmount>eX.MAX_BUFFERED_AMOUNT)return this._buffering=!0,setTimeout(()=>{this._buffering=!1,this._tryBuffer()},50),!1;try{this.dataChannel.send(e)}catch(e){return eO.error(`DC#:${this.connectionId} Error when sending:`,e),this._buffering=!0,this.close(),!1}return!0}// Try to send the first message in the buffer. +_tryBuffer(){if(!this.open||0===this._buffer.length)return;let e=this._buffer[0];this._trySend(e)&&(this._buffer.shift(),this._bufferSize=this._buffer.length,this._tryBuffer())}close(e){if(e?.flush){this.send({__peerData:{type:"close"}});return}this._buffer=[],this._bufferSize=0,super.close()}constructor(...e){super(...e),this._buffer=[],this._bufferSize=0,this._buffering=!1}}class eQ extends eq{close(e){super.close(e),this._chunkedData={}}// Handles a DataChannel message. +_handleDataMessage({data:e}){let t=i(e),n=t.__peerData;if(n){if("close"===n.type){this.close();return}// Chunked data -- piece things back together. +// @ts-ignore +this._handleChunk(t);return}this.emit("data",t)}_handleChunk(e){let t=e.__peerData,n=this._chunkedData[t]||{data:[],count:0,total:e.total};if(n.data[e.n]=new Uint8Array(e.data),n.count++,this._chunkedData[t]=n,n.total===n.count){// Clean up before making the recursive call to `_handleDataMessage`. +delete this._chunkedData[t];// We've received all the chunks--time to construct the complete data. +// const data = new Blob(chunkInfo.data); +let e=function(e){let t=0;for(let n of e)t+=n.byteLength;let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.byteLength;return n}(n.data);this._handleDataMessage({data:e})}}_send(e,t){let n=o(e);if(!t&&n.byteLength>this.chunker.chunkedMTU){this._sendChunks(n);return}this._bufferedSend(n)}_sendChunks(e){let t=this.chunker.chunk(e);for(let e of(eO.log(`DC#${this.connectionId} Try to send ${t.length} chunks...`),t))this.send(e,!0)}constructor(e,t,r){super(e,t,r),this.chunker=new n,this.serialization=I.Binary,this._chunkedData={}}}class eZ extends eq{_handleDataMessage({data:e}){super.emit("data",e)}_send(e,t){this._bufferedSend(e)}constructor(...e){super(...e),this.serialization=I.None}}class e0 extends eq{// Handles a DataChannel message. +_handleDataMessage({data:e}){let t=this.parse(this.decoder.decode(e)),n=t.__peerData;if(n&&"close"===n.type){this.close();return}this.emit("data",t)}_send(e,t){let n=this.encoder.encode(this.stringify(e));if(n.byteLength>=eM.chunkedMTU){this.emitError(x.MessageToBig,"Message too big for JSON channel");return}this._bufferedSend(n)}constructor(...e){super(...e),this.serialization=I.JSON,this.encoder=new TextEncoder,this.decoder=new TextDecoder,this.stringify=JSON.stringify,this.parse=JSON.parse}}class e1 extends eG{/** + * The brokering ID of this peer + * + * If no ID was specified in {@apilink Peer | the constructor}, + * this will be `undefined` until the {@apilink PeerEvents | `open`} event is emitted. + */get id(){return this._id}get options(){return this._options}get open(){return this._open}/** + * @internal + */get socket(){return this._socket}/** + * A hash of all connections associated with this peer, keyed by the remote peer's ID. + * @deprecated + * Return type will change from Object to Map + */get connections(){let e=Object.create(null);for(let[t,n]of this._connections)e[t]=n;return e}/** + * true if this peer and all of its connections can no longer be used. + */get destroyed(){return this._destroyed}/** + * false if there is an active connection to the PeerServer. + */get disconnected(){return this._disconnected}_createServerConnection(){let e=new eJ(this._options.secure,this._options.host,this._options.port,this._options.path,this._options.key,this._options.pingInterval);return e.on(M.Message,e=>{this._handleMessage(e)}),e.on(M.Error,e=>{this._abort(E.SocketError,e)}),e.on(M.Disconnected,()=>{this.disconnected||(this.emitError(E.Network,"Lost connection to server."),this.disconnect())}),e.on(M.Close,()=>{this.disconnected||this._abort(E.SocketClosed,"Underlying socket is already closed.")}),e}/** Initialize a connection with the server. */_initialize(e){this._id=e,this.socket.start(e,this._options.token)}/** Handles messages from the server. */_handleMessage(e){let t=e.type,n=e.payload,r=e.src;switch(t){case O.Open:this._lastServerId=this.id,this._open=!0,this.emit("open",this.id);break;case O.Error:this._abort(E.ServerError,n.msg);break;case O.IdTaken:this._abort(E.UnavailableID,`ID "${this.id}" is taken`);break;case O.InvalidKey:this._abort(E.InvalidKey,`API KEY "${this._options.key}" is invalid`);break;case O.Leave:eO.log(`Received leave message from ${r}`),this._cleanupPeer(r),this._connections.delete(r);break;case O.Expire:this.emitError(E.PeerUnavailable,`Could not connect to peer ${r}`);break;case O.Offer:{// we should consider switching this to CALL/CONNECT, but this is the least breaking option. +let e=n.connectionId,t=this.getConnection(r,e);// Create a new connection. +if(t&&(t.close(),eO.warn(`Offer received for existing Connection ID:${e}`)),n.type===P.Media){let i=new eY(r,this,{connectionId:e,_payload:n,metadata:n.metadata});t=i,this._addConnection(r,t),this.emit("call",i)}else if(n.type===P.Data){let i=new this._serializers[n.serialization](r,this,{connectionId:e,_payload:n,metadata:n.metadata,label:n.label,serialization:n.serialization,reliable:n.reliable});t=i,this._addConnection(r,t),this.emit("connection",i)}else{eO.warn(`Received malformed connection type:${n.type}`);return}// Find messages. +let i=this._getMessages(e);for(let e of i)t.handleMessage(e);break}default:{if(!n){eO.warn(`You received a malformed message from ${r} of type ${t}`);return}let i=n.connectionId,o=this.getConnection(r,i);o&&o.peerConnection?o.handleMessage(e):i?this._storeMessage(i,e):eO.warn("You received an unrecognized message:",e)}}}/** Stores messages without a set up connection, to be claimed later. */_storeMessage(e,t){this._lostMessages.has(e)||this._lostMessages.set(e,[]),this._lostMessages.get(e).push(t)}/** + * Retrieve messages from lost message store + * @internal + *///TODO Change it to private +_getMessages(e){let t=this._lostMessages.get(e);return t?(this._lostMessages.delete(e),t):[]}/** + * Connects to the remote peer specified by id and returns a data connection. + * @param peer The brokering ID of the remote peer (their {@apilink Peer.id}). + * @param options for specifying details about Peer Connection + */connect(e,t={}){if(t={serialization:"default",...t},this.disconnected){eO.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect, or call reconnect on this peer if you believe its ID to still be available."),this.emitError(E.Disconnected,"Cannot connect to new Peer after disconnecting from server.");return}let n=new this._serializers[t.serialization](e,this,t);return this._addConnection(e,n),n}/** + * Calls the remote peer specified by id and returns a media connection. + * @param peer The brokering ID of the remote peer (their peer.id). + * @param stream The caller's media stream + * @param options Metadata associated with the connection, passed in by whoever initiated the connection. + */call(e,t,n={}){if(this.disconnected){eO.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect."),this.emitError(E.Disconnected,"Cannot connect to new Peer after disconnecting from server.");return}if(!t){eO.error("To call a peer, you must provide a stream from your browser's `getUserMedia`.");return}let r=new eY(e,this,{...n,_stream:t});return this._addConnection(e,r),r}/** Add a data/media connection to this peer. */_addConnection(e,t){eO.log(`add connection ${t.type}:${t.connectionId} to peerId:${e}`),this._connections.has(e)||this._connections.set(e,[]),this._connections.get(e).push(t)}//TODO should be private +_removeConnection(e){let t=this._connections.get(e.peer);if(t){let n=t.indexOf(e);-1!==n&&t.splice(n,1)}//remove from lost messages +this._lostMessages.delete(e.connectionId)}/** Retrieve a data/media connection for this peer. */getConnection(e,t){let n=this._connections.get(e);if(!n)return null;for(let e of n)if(e.connectionId===t)return e;return null}_delayedAbort(e,t){setTimeout(()=>{this._abort(e,t)},0)}/** + * Emits an error message and destroys the Peer. + * The Peer is not destroyed if it's in a disconnected state, in which case + * it retains its disconnected state and its existing connections. + */_abort(e,t){eO.error("Aborting!"),this.emitError(e,t),this._lastServerId?this.disconnect():this.destroy()}/** + * Destroys the Peer: closes all active connections as well as the connection + * to the server. + * + * :::caution + * This cannot be undone; the respective peer object will no longer be able + * to create or receive any connections, its ID will be forfeited on the server, + * and all of its data and media connections will be closed. + * ::: + */destroy(){this.destroyed||(eO.log(`Destroy peer with ID:${this.id}`),this.disconnect(),this._cleanup(),this._destroyed=!0,this.emit("close"))}/** Disconnects every connection on this peer. */_cleanup(){for(let e of this._connections.keys())this._cleanupPeer(e),this._connections.delete(e);this.socket.removeAllListeners()}/** Closes all connections to this peer. */_cleanupPeer(e){let t=this._connections.get(e);if(t)for(let e of t)e.close()}/** + * Disconnects the Peer's connection to the PeerServer. Does not close any + * active connections. + * Warning: The peer can no longer create or accept connections after being + * disconnected. It also cannot reconnect to the server. + */disconnect(){if(this.disconnected)return;let e=this.id;eO.log(`Disconnect peer with ID:${e}`),this._disconnected=!0,this._open=!1,this.socket.close(),this._lastServerId=e,this._id=null,this.emit("disconnected",e)}/** Attempts to reconnect with the same ID. + * + * Only {@apilink Peer.disconnect | disconnected peers} can be reconnected. + * Destroyed peers cannot be reconnected. + * If the connection fails (as an example, if the peer's old ID is now taken), + * the peer's existing connections will not close, but any associated errors events will fire. + */reconnect(){if(this.disconnected&&!this.destroyed)eO.log(`Attempting reconnection to server with ID ${this._lastServerId}`),this._disconnected=!1,this._initialize(this._lastServerId);else if(this.destroyed)throw Error("This peer cannot reconnect to the server. It has already been destroyed.");else if(this.disconnected||this.open)throw Error(`Peer ${this.id} cannot reconnect because it is not disconnected from the server!`);else eO.error("In a hurry? We're still trying to make the initial connection!")}/** + * Get a list of available peer IDs. If you're running your own server, you'll + * want to set allow_discovery: true in the PeerServer options. If you're using + * the cloud server, email team@peerjs.com to get the functionality enabled for + * your key. + */listAllPeers(e=e=>{}){this._api.listAllPeers().then(t=>e(t)).catch(e=>this._abort(E.ServerError,e))}constructor(e,t){let n;// Sanity checks +// Ensure WebRTC supported +if(super(),this._serializers={raw:eZ,json:e0,binary:eQ,"binary-utf8":eQ,default:eQ},this._id=null,this._lastServerId=null,// States. +this._destroyed=!1// Connections have been killed +,this._disconnected=!1// Connection to PeerServer killed but P2P connections still active +,this._open=!1// Sockets and such are not yet open. +,this._connections=new Map// All connections for this peer. +,this._lostMessages=new Map// src => [list of messages] +,e&&e.constructor==Object?t=e:e&&(n=e.toString()),// Configurize options +t={debug:0,host:eM.CLOUD_HOST,port:eM.CLOUD_PORT,path:"/",key:e1.DEFAULT_KEY,token:eM.randomToken(),config:eM.defaultConfig,referrerPolicy:"strict-origin-when-cross-origin",serializers:{},...t},this._options=t,this._serializers={...this._serializers,...this.options.serializers},"/"===this._options.host&&(this._options.host=window.location.hostname),this._options.path&&("/"!==this._options.path[0]&&(this._options.path="/"+this._options.path),"/"!==this._options.path[this._options.path.length-1]&&(this._options.path+="/")),void 0===this._options.secure&&this._options.host!==eM.CLOUD_HOST?this._options.secure=eM.isSecure():this._options.host==eM.CLOUD_HOST&&(this._options.secure=!0),this._options.logFunction&&eO.setLogFunction(this._options.logFunction),eO.logLevel=this._options.debug||0,this._api=new eK(t),this._socket=this._createServerConnection(),!eM.supports.audioVideo&&!eM.supports.data){this._delayedAbort(E.BrowserIncompatible,"The current browser does not support WebRTC");return}// Ensure alphanumeric id +if(n&&!eM.validateId(n)){this._delayedAbort(E.InvalidID,`ID "${n}" is invalid`);return}n?this._initialize(n):this._api.retrieveId().then(e=>this._initialize(e)).catch(e=>this._abort(E.ServerError,e))}}e1.DEFAULT_KEY="peerjs",window.peerjs={Peer:e1,util:eM},/** @deprecated Should use peerjs namespace */window.Peer=e1})();//# sourceMappingURL=peerjs.min.js.map + +//# sourceMappingURL=peerjs.min.js.map diff --git a/examples/bun-stream-server/container/extension/streaming.html b/examples/bun-stream-server/container/extension/streaming.html new file mode 100644 index 0000000..e4f4724 --- /dev/null +++ b/examples/bun-stream-server/container/extension/streaming.html @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/examples/bun-stream-server/container/extension/streaming.js b/examples/bun-stream-server/container/extension/streaming.js new file mode 100644 index 0000000..f0f338b --- /dev/null +++ b/examples/bun-stream-server/container/extension/streaming.js @@ -0,0 +1,746 @@ +/** + * Chrome Extension Streaming Handler (streaming.js) + * + * FILE STRUCTURE: + * - background.js: Creates hidden tab with streaming.html when extension loads + * - streaming.html: Loads PeerJS library and this streaming.js file + * - streaming.js: Contains INITIALIZE() function and connection tracking logic + * + * HOW THE SYSTEM WORKS: + * + * 1. CONTAINER STARTUP: + * - Container server starts Puppeteer browser with this Chrome extension + * - background.js creates a hidden tab loading streaming.html + * - streaming.html loads peer.js (PeerJS library) and this streaming.js file + * + * 2. PUPPETEER INTEGRATION: + * - Container server calls page.evaluate() to execute INITIALIZE() function + * - This function captures the current tab's video/audio stream + * - Creates PeerJS connection to stream to remote peer (like a receiver device) + * + * 3. CONNECTION TRACKING SYSTEM: + * - window.activeConnections tracks all active streaming connections + * - Container server polls this every 15 seconds via page.evaluate() + * - When connections drop to 0, starts 60-second shutdown timer + * - If no new connections within grace period, browser shuts down automatically + * + * 4. LIFECYCLE MANAGEMENT: + * - Browser stays alive as long as streams are active + * - Automatically shuts down when unused (resource efficient) + * - Can handle multiple simultaneous streams to different peers + * + * ARCHITECTURE FLOW: + * Stream Request → Container Server → Puppeteer → Chrome Extension → PeerJS → Remote Peer + * ↘ Connection Monitoring ↙ + * (Polls window.activeConnections) + * + * IMPORTANT NOTES: + * - This runs in the Chrome extension context (isolated from normal web pages) + * - Has special permissions for tab capture (see manifest.json) + * - INITIALIZE function must be globally accessible for Puppeteer's page.evaluate() + * - Connection tracking is critical for automatic resource management + */ + +/** + * INITIALIZE gets called within the context of the chrome extension + * by puppeteer calling evaluate on the extension's background page (streaming.html) + * + * captures the active puppeteer tab into a media stream that we use + * to call the remote peer using peerjs + * + * @param {Object} params - Parameters from container server + * @param {string} params.srcPeerId - This browser's PeerJS ID (UUID) + * @param {string} params.destPeerId - Remote peer's PeerJS ID (receiver) + */ + +// Initialize connection tracking for container server monitoring +// The container server polls this Set to determine when to shut down +console.log('[INIT] Initializing window.activeConnections Set'); +window.activeConnections = new Set(); + +// Debug tracking that container server can read +console.log('[INIT] Initializing window.streamingDebug object'); +window.streamingDebug = { + initializeCalled: false, + addConnectionCalled: false, + lastPeerId: null, + lastError: null, + callCount: 0, + scriptLoadTime: new Date().toISOString(), + peerJsAvailable: typeof Peer !== 'undefined', + chromeTabCaptureAvailable: typeof chrome !== 'undefined' && typeof chrome.tabCapture !== 'undefined', + captureDiagnostics: null, + testVideoDiagnostics: null, + eventLog: [] +}; + +function recordDebugEvent(event, details = {}) { + const entry = { + event, + details, + timestamp: new Date().toISOString() + }; + window.streamingDebug.eventLog.push(entry); + if (window.streamingDebug.eventLog.length > 40) { + window.streamingDebug.eventLog.shift(); + } +} + +console.log('[INIT] streaming.js loaded successfully'); +console.log('[INIT] PeerJS available:', window.streamingDebug.peerJsAvailable); +console.log('[INIT] Chrome tabCapture available:', window.streamingDebug.chromeTabCaptureAvailable); +console.log('[INIT] Initial activeConnections size:', window.activeConnections.size); + +/** + * Add a peer connection to the tracking set + * Container server monitors window.activeConnections.size via page.evaluate() + */ +function addConnection(peerId) { + console.log(`[CONNECTION] Adding connection for peer: ${peerId}`); + console.log(`[CONNECTION] activeConnections before add:`, Array.from(window.activeConnections)); + console.log(`[CONNECTION] activeConnections size before add:`, window.activeConnections.size); + + window.activeConnections.add(peerId); + + console.log(`[CONNECTION] activeConnections after add:`, Array.from(window.activeConnections)); + console.log(`[CONNECTION] activeConnections size after add:`, window.activeConnections.size); + console.log(`[CONNECTION] Connection opened to ${peerId}. Active: ${window.activeConnections.size}`); + + // Update debug info + window.streamingDebug.addConnectionCalled = true; + window.streamingDebug.lastPeerId = peerId; + recordDebugEvent('connection_added', { peerId, size: window.activeConnections.size }); +} + +/** + * Remove a peer connection from tracking set + * When this reaches 0, container server starts shutdown grace period + */ +function removeConnection(peerId) { + console.log(`[CONNECTION] Removing connection for peer: ${peerId}`); + console.log(`[CONNECTION] activeConnections before remove:`, Array.from(window.activeConnections)); + console.log(`[CONNECTION] activeConnections size before remove:`, window.activeConnections.size); + + const wasPresent = window.activeConnections.has(peerId); + window.activeConnections.delete(peerId); + recordDebugEvent('connection_removed', { peerId, wasPresent, size: window.activeConnections.size }); + + console.log(`[CONNECTION] Was peer present before removal: ${wasPresent}`); + console.log(`[CONNECTION] activeConnections after remove:`, Array.from(window.activeConnections)); + console.log(`[CONNECTION] activeConnections size after remove:`, window.activeConnections.size); + console.log(`[CONNECTION] Connection closed to ${peerId}. Active: ${window.activeConnections.size}`); +} + +async function INITIALIZE({ srcPeerId, destPeerId, iceServers = [] }) { + console.log(`[INITIALIZE] ========== STARTING INITIALIZATION ==========`); + console.log(`[INITIALIZE] Function called with params:`, { srcPeerId, destPeerId }); + console.log(`[INITIALIZE] srcPeerId type: ${typeof srcPeerId}, value: "${srcPeerId}"`); + console.log(`[INITIALIZE] destPeerId type: ${typeof destPeerId}, value: "${destPeerId}"`); + + // Mark that INITIALIZE was called + window.streamingDebug.initializeCalled = true; + window.streamingDebug.callCount++; + window.streamingDebug.lastPeerId = destPeerId; + window.streamingDebug.lastError = null; // Reset error state + window.streamingDebug.captureDiagnostics = null; + window.streamingDebug.testVideoDiagnostics = null; + recordDebugEvent('initialize_called', { srcPeerId, destPeerId, iceServerCount: Array.isArray(iceServers) ? iceServers.length : 0 }); + + console.log(`[INITIALIZE] Updated debug info - call count: ${window.streamingDebug.callCount}`); + console.log(`[INITIALIZE] Current activeConnections:`, Array.from(window.activeConnections)); + console.log(`[INITIALIZE] Current activeConnections size:`, window.activeConnections.size); + + try { + // First, find and activate the target tab to satisfy activeTab permission + console.log(`[TAB_ACTIVATION] Finding target tab to activate extension...`); + + const tabs = await new Promise((resolve, reject) => { + chrome.tabs.query({}, (tabs) => { + if (chrome.runtime.lastError) { + console.error(`[TAB_ACTIVATION] ❌ Failed to query tabs:`, chrome.runtime.lastError); + reject(new Error(`Failed to query tabs: ${chrome.runtime.lastError.message}`)); + return; + } + console.log(`[TAB_ACTIVATION] Found ${tabs.length} tabs`); + tabs.forEach((tab, index) => { + console.log(`[TAB_ACTIVATION] Tab ${index}: ${tab.url} (active: ${tab.active}, id: ${tab.id})`); + }); + resolve(tabs); + }); + }); + + // Find the active tab (the one Puppeteer is controlling) + const activeTab = tabs.find(tab => tab.active) || tabs.find(tab => !tab.url.startsWith('chrome-extension://')); + + if (!activeTab) { + throw new Error('No suitable target tab found for capture'); + } + + console.log(`[TAB_ACTIVATION] Selected target tab: ${activeTab.url} (id: ${activeTab.id})`); + + // Programmatically activate the extension for this tab by injecting a small script + // This satisfies the activeTab permission requirement + console.log(`[TAB_ACTIVATION] Activating extension for tab ${activeTab.id}...`); + + try { + await new Promise((resolve, reject) => { + chrome.scripting.executeScript({ + target: { tabId: activeTab.id }, + func: () => { + // This script injection activates the extension for this tab + console.log('[TAB_INJECTION] Extension activated for this tab'); + return true; + } + }, (results) => { + if (chrome.runtime.lastError) { + console.warn(`[TAB_ACTIVATION] ⚠️ Script injection failed (may be normal for some pages):`, chrome.runtime.lastError.message); + // Don't reject - some pages can't be injected into, but tabCapture might still work + resolve(); + } else { + console.log(`[TAB_ACTIVATION] ✅ Successfully activated extension for tab`); + resolve(); + } + }); + }); + } catch (activationError) { + console.warn(`[TAB_ACTIVATION] ⚠️ Extension activation failed, proceeding anyway:`, activationError.message); + // Continue - tabCapture might still work even without successful injection + } + + // Now attempt tab capture + console.log(`[TAB_CAPTURE] Starting tab capture for tab ${activeTab.id}...`); + console.log(`[TAB_CAPTURE] chrome object available:`, typeof chrome !== 'undefined'); + console.log(`[TAB_CAPTURE] chrome.tabCapture available:`, typeof chrome !== 'undefined' && typeof chrome.tabCapture !== 'undefined'); + + // Preflight: stop any previous capture to avoid "Cannot capture a tab with an active stream" + try { + if (window.currentCaptureStream) { + console.log('[TAB_CAPTURE] Found previous capture stream, stopping it...'); + window.currentCaptureStream.getTracks().forEach(t => t.stop()); + window.currentCaptureStream = null; + } + } catch (e) { + console.warn('[TAB_CAPTURE] Preflight stop error:', e && e.message); + } + + let stream; + + // Wait until no active capture is registered for this tab + async function waitForNoActiveCapture(tabId, maxTries = 20, delayMs = 100) { + for (let i = 0; i < maxTries; i++) { + const { anyActive, tabsSnapshot } = await new Promise((res) => { + chrome.tabCapture.getCapturedTabs((tabs) => { + try { + const list = (tabs || []).map(t => ({ tabId: t.tabId, status: t.status, fullscreen: t.fullscreen })); + const active = list.some(t => t.status === 'active' && t.tabId === tabId); + console.log('[TAB_CAPTURE] Poll', i + 1, '/', maxTries, 'captured tabs:', list); + res({ anyActive: active, tabsSnapshot: list }); + } catch (e) { + console.warn('[TAB_CAPTURE] getCapturedTabs inspect error:', e && e.message); + res({ anyActive: false, tabsSnapshot: [] }); + } + }); + }); + if (!anyActive) return; + await new Promise(r => setTimeout(r, delayMs)); + } + console.warn('[TAB_CAPTURE] Still active after wait; proceeding anyway'); + } + + await waitForNoActiveCapture(activeTab.id); + + // First try standard tabCapture.capture + try { + console.log('[TAB_CAPTURE] About to call chrome.tabCapture.capture synchronously'); + console.log('[TAB_CAPTURE] Chrome tabCapture API available:', typeof chrome.tabCapture !== 'undefined'); + console.log('[TAB_CAPTURE] Extension context check:', { + hasChrome: typeof chrome !== 'undefined', + hasTabCapture: typeof chrome !== 'undefined' && typeof chrome.tabCapture !== 'undefined', + hasActiveTab: typeof chrome !== 'undefined' && typeof chrome.activeTab !== 'undefined', + extensionId: chrome.runtime?.id || 'unknown' + }); + + stream = await new Promise((resolve, reject) => { + console.log(`[TAB_CAPTURE] Attempting tabCapture.capture on active tab...`); + console.log(`[TAB_CAPTURE] Target tab ID: ${activeTab.id}, URL: ${activeTab.url}`); + + chrome.tabCapture.capture( + { + video: true, + audio: true, + videoConstraints: { + mandatory: { + minWidth: 1280, + minHeight: 720, + maxWidth: 1920, + maxHeight: 1080, + maxFrameRate: 30 + } + } + }, + (capturedStream) => { + if (capturedStream) { + console.log(`[TAB_CAPTURE] ✅ capture() returned a stream`); + console.log(`[TAB_CAPTURE] Stream details:`, { + id: capturedStream.id, + active: capturedStream.active, + tracks: capturedStream.getTracks().length, + videoTracks: capturedStream.getVideoTracks().length, + audioTracks: capturedStream.getAudioTracks().length + }); + resolve(capturedStream); + } else { + const error = chrome.runtime.lastError; + console.warn(`[TAB_CAPTURE] capture() failed:`, error && error.message); + console.warn(`[TAB_CAPTURE] Last error details:`, { + message: error?.message, + stack: error?.stack, + toString: error?.toString() + }); + reject(new Error(error ? error.message : 'Unknown capture error')); + } + } + ); + }); + } catch (capErr) { + console.warn(`[TAB_CAPTURE] capture() failed, falling back to getMediaStreamId + getUserMedia:`, capErr.message); + + // Fallback: getMediaStreamId + getUserMedia with Chrome-specific constraints + const streamId = await new Promise((resolve, reject) => { + try { + // targetTabId may not be supported on all channels; try with and without + const opts = { targetTabId: activeTab.id }; + console.log(`[TAB_CAPTURE] Requesting media stream id for tab ${activeTab.id}...`); + chrome.tabCapture.getMediaStreamId(opts, (id) => { + if (chrome.runtime.lastError || !id) { + const err1 = chrome.runtime.lastError && chrome.runtime.lastError.message; + console.warn(`[TAB_CAPTURE] getMediaStreamId with targetTabId failed:`, err1); + chrome.tabCapture.getMediaStreamId((id2) => { + if (chrome.runtime.lastError || !id2) { + const err2 = chrome.runtime.lastError && chrome.runtime.lastError.message; + reject(new Error(err2 || 'Failed to obtain mediaStreamId')); + } else { + resolve(id2); + } + }); + } else { + resolve(id); + } + }); + } catch (e) { + reject(e); + } + }); + + console.log(`[TAB_CAPTURE] Obtained mediaStreamId: ${streamId}`); + + try { + // @ts-ignore chrome-specific constraints + stream = await navigator.mediaDevices.getUserMedia({ + audio: { + mandatory: { + chromeMediaSource: 'tab', + chromeMediaSourceId: streamId, + } + }, + video: { + mandatory: { + chromeMediaSource: 'tab', + chromeMediaSourceId: streamId, + maxWidth: 1920, + maxHeight: 1080, + maxFrameRate: 30 + } + } + }); + console.log(`[TAB_CAPTURE] ✅ getUserMedia returned a stream`); + } catch (gumErr) { + console.error(`[TAB_CAPTURE] ❌ getUserMedia with tab source failed:`, gumErr); + throw new Error(`Failed to capture the tab: ${gumErr.message}`); + } + } + + // At this point we have a valid stream + console.log(`[TAB_CAPTURE] Stream has ${stream.getTracks().length} tracks`); + + // Enhanced stream debugging + stream.getTracks().forEach((track, index) => { + console.log(`[TAB_CAPTURE] Track ${index}:`, { + kind: track.kind, + label: track.label, + enabled: track.enabled, + readyState: track.readyState, + muted: track.muted, + constraints: track.getConstraints ? track.getConstraints() : 'N/A', + settings: track.getSettings ? track.getSettings() : 'N/A' + }); + }); + window.streamingDebug.captureDiagnostics = { + streamId: stream.id, + active: stream.active, + trackCount: stream.getTracks().length, + videoTrackCount: stream.getVideoTracks().length, + audioTrackCount: stream.getAudioTracks().length, + tracks: stream.getTracks().map((track) => ({ + kind: track.kind, + label: track.label, + enabled: track.enabled, + readyState: track.readyState, + muted: track.muted, + settings: track.getSettings ? track.getSettings() : null + })) + }; + recordDebugEvent('capture_stream_created', window.streamingDebug.captureDiagnostics); + + // Test if the stream is actually producing data + if (stream.getVideoTracks().length > 0) { + const videoTrack = stream.getVideoTracks()[0]; + const videoSettings = videoTrack.getSettings(); + const videoConstraints = videoTrack.getConstraints ? videoTrack.getConstraints() : null; + + console.log('[TAB_CAPTURE] Video track details:', { + width: videoSettings.width, + height: videoSettings.height, + frameRate: videoSettings.frameRate, + deviceId: videoSettings.deviceId, + aspectRatio: videoSettings.aspectRatio, + facingMode: videoSettings.facingMode, + resizeMode: videoSettings.resizeMode, + allSettings: videoSettings, + constraints: videoConstraints + }); + + // Check if dimensions are 0 which indicates capture failure + if (videoSettings.width === 0 || videoSettings.height === 0) { + console.error('[TAB_CAPTURE] ❌ Video track has zero dimensions - capture likely failed!'); + console.error('[TAB_CAPTURE] This usually means:'); + console.error('[TAB_CAPTURE] 1. Tab content is not being rendered (headless browser issue)'); + console.error('[TAB_CAPTURE] 2. Extension permissions not properly granted'); + console.error('[TAB_CAPTURE] 3. Chrome tab capture API not working in this environment'); + } + window.streamingDebug.captureDiagnostics = { + ...window.streamingDebug.captureDiagnostics, + videoTrackDetails: { + width: videoSettings.width || 0, + height: videoSettings.height || 0, + frameRate: videoSettings.frameRate || null, + aspectRatio: videoSettings.aspectRatio || null, + zeroDimensions: videoSettings.width === 0 || videoSettings.height === 0 + } + }; + recordDebugEvent('capture_video_track_inspected', window.streamingDebug.captureDiagnostics.videoTrackDetails); + + // Create a test video element to verify the stream works + try { + const testVideo = document.createElement('video'); + testVideo.srcObject = stream; + testVideo.muted = true; // Required for autoplay + window.streamingDebug.testVideoDiagnostics = { + attached: true, + playStarted: false, + waiting: false, + stalled: false, + metadataLoaded: false, + videoWidth: 0, + videoHeight: 0, + hasNonZeroPixels: null, + samplePixels: null, + error: null + }; + + testVideo.onloadedmetadata = () => { + const metadata = { + videoWidth: testVideo.videoWidth, + videoHeight: testVideo.videoHeight, + duration: testVideo.duration, + readyState: testVideo.readyState, + networkState: testVideo.networkState + }; + console.log('[TAB_CAPTURE] Test video metadata loaded:', JSON.stringify(metadata)); + window.streamingDebug.testVideoDiagnostics = { + ...window.streamingDebug.testVideoDiagnostics, + metadataLoaded: true, + ...metadata + }; + recordDebugEvent('test_video_metadata_loaded', metadata); + + // Try to play the video to see if it actually has content + testVideo.play().then(() => { + console.log('[TAB_CAPTURE] ✅ Test video can play'); + window.streamingDebug.testVideoDiagnostics = { + ...window.streamingDebug.testVideoDiagnostics, + playStarted: true + }; + recordDebugEvent('test_video_play_started'); + + // Check if video is actually updating by sampling pixels + setTimeout(() => { + try { + const canvas = document.createElement('canvas'); + canvas.width = testVideo.videoWidth || 100; + canvas.height = testVideo.videoHeight || 100; + const ctx = canvas.getContext('2d'); + ctx.drawImage(testVideo, 0, 0); + const imageData = ctx.getImageData(0, 0, 10, 10); + const hasNonZeroPixels = imageData.data.some(pixel => pixel > 0); + const samplePixels = Array.from(imageData.data.slice(0, 20)); + console.log('[TAB_CAPTURE] Video width:', testVideo.videoWidth); + console.log('[TAB_CAPTURE] Video height:', testVideo.videoHeight); + console.log('[TAB_CAPTURE] Video has non-zero pixels:', hasNonZeroPixels); + console.log('[TAB_CAPTURE] Sample pixel data:', samplePixels); + window.streamingDebug.testVideoDiagnostics = { + ...window.streamingDebug.testVideoDiagnostics, + videoWidth: testVideo.videoWidth, + videoHeight: testVideo.videoHeight, + hasNonZeroPixels, + samplePixels + }; + recordDebugEvent('test_video_pixels_sampled', { + videoWidth: testVideo.videoWidth, + videoHeight: testVideo.videoHeight, + hasNonZeroPixels + }); + } catch (canvasErr) { + console.warn('[TAB_CAPTURE] Could not sample video pixels:', canvasErr); + window.streamingDebug.testVideoDiagnostics = { + ...window.streamingDebug.testVideoDiagnostics, + error: `Pixel sample failed: ${canvasErr.message}` + }; + recordDebugEvent('test_video_pixel_sample_failed', { message: canvasErr.message }); + } + }, 1000); + + }).catch((playErr) => { + console.error('[TAB_CAPTURE] Test video play failed:', playErr); + window.streamingDebug.testVideoDiagnostics = { + ...window.streamingDebug.testVideoDiagnostics, + error: `Play failed: ${playErr.message || String(playErr)}` + }; + recordDebugEvent('test_video_play_failed', { + message: playErr.message || String(playErr) + }); + }); + }; + + testVideo.onerror = (e) => { + console.error('[TAB_CAPTURE] Test video error:', e); + console.error('[TAB_CAPTURE] Video error details:', testVideo.error); + window.streamingDebug.testVideoDiagnostics = { + ...window.streamingDebug.testVideoDiagnostics, + error: testVideo.error ? `Video error code ${testVideo.error.code}` : 'Unknown video error' + }; + recordDebugEvent('test_video_error', { + errorCode: testVideo.error ? testVideo.error.code : null + }); + }; + + testVideo.onwaiting = () => { + console.log('[TAB_CAPTURE] Test video waiting for data...'); + window.streamingDebug.testVideoDiagnostics = { + ...window.streamingDebug.testVideoDiagnostics, + waiting: true + }; + recordDebugEvent('test_video_waiting'); + }; + + testVideo.onstalled = () => { + console.log('[TAB_CAPTURE] Test video stalled'); + window.streamingDebug.testVideoDiagnostics = { + ...window.streamingDebug.testVideoDiagnostics, + stalled: true + }; + recordDebugEvent('test_video_stalled'); + }; + + } catch (testErr) { + console.warn('[TAB_CAPTURE] Could not create test video element:', testErr); + window.streamingDebug.testVideoDiagnostics = { + attached: false, + error: `Test video creation failed: ${testErr.message}` + }; + recordDebugEvent('test_video_create_failed', { message: testErr.message }); + } + } else { + console.error('[TAB_CAPTURE] ❌ No video tracks in stream!'); + window.streamingDebug.captureDiagnostics = { + ...window.streamingDebug.captureDiagnostics, + error: 'No video tracks in stream' + }; + recordDebugEvent('capture_stream_missing_video_track'); + } + + // Store globally to detect active capture on subsequent init calls + try { + window.currentCaptureStream = stream; + console.log('[TAB_CAPTURE] currentCaptureStream set'); + stream.getTracks().forEach((track, index) => { + track.onended = () => { + console.log('[TAB_CAPTURE] Track ended:', track.kind, track.label); + const allEnded = !window.currentCaptureStream || window.currentCaptureStream.getTracks().every(t => t.readyState === 'ended'); + if (allEnded) { + console.log('[TAB_CAPTURE] All tracks ended, clearing currentCaptureStream'); + window.currentCaptureStream = null; + } + }; + }); + } catch (e) { + console.warn('[TAB_CAPTURE] Could not set global capture stream:', e && e.message); + } + + // Create PeerJS peer with our assigned ID + console.log(`[PEERJS] Creating peer with ID: "${srcPeerId}"`); + console.log(`[PEERJS] Peer constructor available:`, typeof Peer !== 'undefined'); + + const peer = new Peer(srcPeerId, { + debug: 3, + config: { + iceServers: Array.isArray(iceServers) ? iceServers : [] + } + }); + console.log(`[PEERJS] Peer object created:`, peer); + + // Wait for peer to connect to PeerJS signaling server + console.log(`[PEERJS] Waiting for peer to connect to signaling server...`); + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + console.error(`[PEERJS] ❌ Timeout waiting for peer to open`); + reject(new Error('Timeout waiting for peer to connect')); + }, 30000); // 30 second timeout + + peer.once('open', (id) => { + clearTimeout(timeout); + console.log(`[PEERJS] ✅ Connected to signaling server with ID: "${id}"`); + console.log(`[PEERJS] Peer ID matches expected: ${id === srcPeerId}`); + resolve(); + }); + + peer.on('error', (error) => { + clearTimeout(timeout); + console.error(`[PEERJS] ❌ Peer error during connection:`, error); + console.error(`[PEERJS] Error type: ${error.type}, message: ${error.message}`); + reject(new Error(`Peer error: ${error.message}`)); + }); + }); + + // Make the call to the destination peer and start connection tracking + console.log(`[CALL] Starting call to destination peer: "${destPeerId}"`); + console.log(`[CALL] Stream object for call:`, stream); + console.log(`[CALL] Stream tracks for call:`, stream.getTracks().map(t => `${t.kind}:${t.label}`)); + + const call = peer.call(destPeerId, stream); + + if (!call) { + console.error(`[CALL] ❌ Failed to create call to ${destPeerId} - call object is null/undefined`); + window.streamingDebug.lastError = 'Failed to create call object'; + return; + } + + console.log(`[CALL] ✅ Call object created successfully:`, call); + console.log(`[CALL] Call peer ID: "${call.peer}"`); + console.log(`[CALL] Call type: "${call.type}"`); + + // Track the connection immediately since we're initiating the call + console.log(`[CALL] Adding connection to tracking before call events...`); + console.log(`[CALL] About to call addConnection with destPeerId: "${destPeerId}"`); + console.log(`[CALL] activeConnections before addConnection:`, Array.from(window.activeConnections)); + + try { + addConnection(destPeerId); + console.log(`[CALL] ✅ addConnection completed successfully`); + console.log(`[CALL] activeConnections after addConnection:`, Array.from(window.activeConnections)); + console.log(`[CALL] activeConnections size after addConnection:`, window.activeConnections.size); + } catch (error) { + console.error(`[CALL] ❌ addConnection failed:`, error); + window.streamingDebug.lastError = `addConnection failed: ${error.message}`; + } + + // Handle call lifecycle events + call.on('stream', (remoteStream) => { + console.log(`[CALL_EVENT] 'stream' event - Received remote stream from ${destPeerId}`); + console.log(`[CALL_EVENT] Remote stream tracks:`, remoteStream.getTracks().map(t => `${t.kind}:${t.label}`)); + // Connection already tracked above, just log that it's bidirectional + }); + + // The 'open' event fires when the call is answered + call.on('open', () => { + console.log(`[CALL_EVENT] 'open' event - Call to ${destPeerId} opened successfully`); + console.log(`[CALL_EVENT] Call is now active and streaming`); + // Connection already tracked above, this is just confirmation + }); + + call.on('close', () => { + console.log(`[CALL_EVENT] 'close' event - Call to ${destPeerId} ended`); + removeConnection(destPeerId); + }); + + call.on('error', (error) => { + console.error(`[CALL_EVENT] 'error' event - Call error with ${destPeerId}:`, error); + console.error(`[CALL_EVENT] Error type: ${error.type}, message: ${error.message}`); + removeConnection(destPeerId); + window.streamingDebug.lastError = `Call error: ${error.message}`; + }); + + // Handle incoming calls (though this extension typically just calls out) + console.log(`[PEER_EVENTS] Setting up peer event handlers...`); + peer.on('call', (incomingCall) => { + console.log(`[PEER_EVENT] 'call' event - Received incoming call from ${incomingCall.peer}`); + addConnection(incomingCall.peer); + + // Auto-answer incoming calls with the same captured stream + console.log(`[PEER_EVENT] Auto-answering incoming call with captured stream`); + incomingCall.answer(stream); + + incomingCall.on('stream', (remoteStream) => { + console.log(`[PEER_EVENT] Incoming call 'stream' event - received remote stream from ${incomingCall.peer}`); + }); + + incomingCall.on('close', () => { + console.log(`[PEER_EVENT] Incoming call 'close' event - call from ${incomingCall.peer} ended`); + removeConnection(incomingCall.peer); + }); + + incomingCall.on('error', (error) => { + console.error(`[PEER_EVENT] Incoming call 'error' event - error from ${incomingCall.peer}:`, error); + removeConnection(incomingCall.peer); + }); + }); + + // Handle peer-level events + peer.on('disconnected', () => { + console.log('[PEER_EVENT] Peer disconnected from signaling server'); + console.log('[PEER_EVENT] Note: existing calls may still be active'); + }); + + peer.on('close', () => { + console.log('[PEER_EVENT] Peer connection closed completely'); + console.log('[PEER_EVENT] Clearing all tracked connections'); + console.log('[PEER_EVENT] activeConnections before clear:', Array.from(window.activeConnections)); + window.activeConnections.clear(); + console.log('[PEER_EVENT] activeConnections after clear:', Array.from(window.activeConnections)); + }); + + console.log(`[INITIALIZE] ✅ Initialization complete. Ready for streaming.`); + console.log(`[INITIALIZE] Final activeConnections:`, Array.from(window.activeConnections)); + console.log(`[INITIALIZE] Final activeConnections size:`, window.activeConnections.size); + console.log(`[INITIALIZE] ========== INITIALIZATION COMPLETE ==========`); + recordDebugEvent('initialize_complete', { + activeConnectionsSize: window.activeConnections.size + }); + + } catch (error) { + console.error(`[INITIALIZE] ❌ INITIALIZATION FAILED:`, error); + console.error(`[INITIALIZE] Error stack:`, error.stack); + window.streamingDebug.lastError = `Initialization failed: ${error.message}`; + recordDebugEvent('initialize_failed', { message: error.message }); + throw error; // Re-throw so container server knows it failed + } +} + +// Log when the script finishes loading +console.log('[INIT] streaming.js script execution complete'); +console.log('[INIT] INITIALIZE function available:', typeof INITIALIZE === 'function'); +console.log('[INIT] Global objects available:', { + Peer: typeof Peer !== 'undefined', + chrome: typeof chrome !== 'undefined', + 'chrome.tabCapture': typeof chrome !== 'undefined' && typeof chrome.tabCapture !== 'undefined' +}); diff --git a/examples/bun-stream-server/container/package-lock.json b/examples/bun-stream-server/container/package-lock.json new file mode 100644 index 0000000..3382184 --- /dev/null +++ b/examples/bun-stream-server/container/package-lock.json @@ -0,0 +1,1167 @@ +{ + "name": "stream-server-container", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "stream-server-container", + "version": "1.0.0", + "dependencies": { + "puppeteer": "^24.4.0", + "tsx": "^4.7.1" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "typescript": "^5.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.9", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.18.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.1.tgz", + "integrity": "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "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", + "license": "Python-2.0" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "license": "Apache-2.0" + }, + "node_modules/bare-events": { + "version": "2.6.1", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.4.1", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-stream/node_modules/streamx": { + "version": "2.22.1", + "license": "MIT", + "optional": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/bare-stream/node_modules/streamx/node_modules/bare-events": { + "version": "2.6.1", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-url": { + "version": "2.2.2", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chromium-bidi": { + "version": "8.0.0", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "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", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1495869", + "license": "BSD-3-Clause" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.4.0", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream/node_modules/pump": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/get-stream/node_modules/pump/node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.4.0", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "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/ip-address": { + "version": "9.0.5", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/progress": { + "version": "2.0.3", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "24.20.0", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.9", + "chromium-bidi": "8.0.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1495869", + "puppeteer-core": "24.20.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.20.0", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.9", + "chromium-bidi": "8.0.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1495869", + "typed-query-selector": "^2.12.0", + "webdriver-bidi-protocol": "0.2.8", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "license": "BSD-3-Clause" + }, + "node_modules/streamx": { + "version": "2.22.0", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/streamx/node_modules/bare-events": { + "version": "2.5.4", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "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", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.19.4", + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.8.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.2.8", + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "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/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "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", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/examples/bun-stream-server/container/package.json b/examples/bun-stream-server/container/package.json index 070f1c3..f084fb9 100644 --- a/examples/bun-stream-server/container/package.json +++ b/examples/bun-stream-server/container/package.json @@ -2,16 +2,16 @@ "name": "stream-server-container", "version": "1.0.0", "private": true, - "type": "module", "scripts": { "start": "tsx src/server.ts", - "dev": "tsx watch src/server.ts" + "dev": "tsx --watch src/server.ts" }, "dependencies": { - "puppeteer": "^21.0.0" + "puppeteer": "^24.20.0", + "tsx": "^4.7.1" }, "devDependencies": { - "tsx": "^4.7.1", + "@types/node": "^22.10.2", "typescript": "^5.0.0" } -} \ No newline at end of file +} \ No newline at end of file diff --git a/examples/bun-stream-server/container/src/server.ts b/examples/bun-stream-server/container/src/server.ts index 0105a70..fee05db 100644 --- a/examples/bun-stream-server/container/src/server.ts +++ b/examples/bun-stream-server/container/src/server.ts @@ -1,65 +1,1132 @@ +/** + * Stream Container Server + * + * This server manages a single browser instance with Puppeteer and monitors + * active WebRTC connections to automatically shut down when no longer needed. + * + * Connection Monitoring Strategy: + * - Chrome extension maintains window.activeConnections Set with peer IDs + * - Container server polls this state every 15 seconds via page.evaluate() + * - When connections drop to 0, starts 60-second grace period + * - If no connections return within grace period, shuts down browser + * - If new connections appear during grace period, cancels shutdown + * + * Browser Lifecycle: + * - Browser launches on first /start-stream request + * - Stays alive as long as connections are active + * - Automatically shuts down after grace period with no connections + * - Can be manually restarted with new /start-stream requests + */ + import puppeteer from 'puppeteer'; +import type { Browser, Page } from 'puppeteer'; +import crypto from 'crypto'; +import http from 'http'; +import url from 'url'; +import { + parseStartStreamRequest, + type IceServerConfig, +} from '../../src/protocol'; -// Simple hello world server to test container setup -const server = Bun.serve({ - port: 8080, - async fetch(req) { - const url = new URL(req.url); - const pathname = url.pathname; +// TypeScript declaration for browser window extensions +declare global { + interface Window { + activeConnections?: Set; + streamingDebug?: { + initializeCalled: boolean; + addConnectionCalled: boolean; + lastPeerId: string | null; + lastError: string | null; + callCount: number; + }; + INITIALIZE?: (params: { srcPeerId: string; destPeerId: string; iceServers?: IceServerConfig[] }) => Promise; + Peer?: any; // PeerJS constructor + } +} - // Health check endpoint - if (pathname === '/health') { - return new Response(JSON.stringify({ - status: 'healthy', - puppeteer: 'imported', - location: process.env.CLOUDFLARE_LOCATION || 'local', - region: process.env.CLOUDFLARE_REGION || 'dev' - }), { - headers: { 'Content-Type': 'application/json' } +const EXTENSION_PATH = './extension'; +let EXTENSION_ID: string | null = null; // Dynamically detected at runtime + +// Connection monitoring configuration +const GRACE_PERIOD_MS = 60000; // 60 seconds +const POLL_INTERVAL_MS = 15000; // 15 seconds + +// Module-level state for persistent browser instance +let browser: Browser | undefined; +let activePage: Page | undefined; +let streamingPage: Page | undefined; +let connectionCheckInterval: NodeJS.Timeout | null = null; +let shutdownTimer: NodeJS.Timeout | null = null; + +function logTrace(traceId: string, event: string, details?: Record) { + if (details) { + console.log(`[trace:${traceId}] ${event}`, details); + return; + } + console.log(`[trace:${traceId}] ${event}`); +} + +function buildTraceHeaders(traceId?: string): HeadersInit | undefined { + if (!traceId) return undefined; + return { + 'x-stream-trace-id': traceId, + }; +} + +/** Utility: Create JSON response */ +function jsonResponse(data: unknown, init: ResponseInit = {}) { + return new Response(JSON.stringify(data), { + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, x-stream-trace-id', + ...(init.headers || {}) + }, + ...init, + }); +} + +async function collectBrowserState(traceId: string) { + const activePageState = activePage + ? await activePage + .evaluate(() => ({ + url: window.location.href, + title: document.title, + visibilityState: document.visibilityState, + readyState: document.readyState, + hasFocus: document.hasFocus(), + })) + .catch((error: Error) => ({ error: error.message })) + : null; + + const extensionState = streamingPage + ? await streamingPage + .evaluate(() => ({ + location: window.location.href, + hasInitialize: typeof window.INITIALIZE === 'function', + activeConnections: window.activeConnections ? Array.from(window.activeConnections) : [], + activeConnectionsSize: window.activeConnections ? window.activeConnections.size : 0, + streamingDebug: window.streamingDebug || null, + peerDefined: typeof window.Peer !== 'undefined', + })) + .catch((error: Error) => ({ error: error.message })) + : null; + + const targetSummary = browser + ? browser.targets().map((target) => ({ + type: target.type(), + url: target.url(), + })) + : []; + + const snapshot = { + browserActive: !!browser, + activePageState, + extensionState, + targetSummary, + monitoringActive: !!connectionCheckInterval, + shutdownTimerActive: !!shutdownTimer, + capturedAt: new Date().toISOString(), + }; + + logTrace(traceId, 'browser_state_snapshot', snapshot as Record); + return snapshot; +} + +/** Build Puppeteer launch options */ +function buildLaunchOptions() { + const absoluteExtensionPath = require('path').resolve(EXTENSION_PATH); + + return { + headless: 'new' as any, // Use new headless mode for better container and extension support + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + `--disable-extensions-except=${absoluteExtensionPath}`, + `--load-extension=${absoluteExtensionPath}`, + '--webrtc-udp-port-range=10000-10100', + '--autoplay-policy=no-user-gesture-required', + '--disable-web-security', // Allow cross-origin requests for streaming + '--remote-debugging-port=9222', // Enable remote debugging + '--auto-accept-this-tab-capture', + // Extension permission flags for headless mode + '--enable-automation', // Enable automation extensions + '--disable-extensions-file-access-check', // Allow file access for extensions + '--allow-running-insecure-content', // Allow extensions to run in secure contexts + '--disable-component-extensions-with-background-pages=false', // Enable component extensions + '--enable-extension-activity-logging', // Better extension debugging + '--allow-file-access-from-files', // Allow file access for extensions + // Grant extension permissions automatically in headless + '--allowlisted-extension-id=jjndjgheafjngoipoacpjgeicjeomjli', // Whitelist our extension by key from manifest.json + // Container-optimized flags + '--disable-background-timer-throttling', + '--disable-backgrounding-occluded-windows', + '--disable-renderer-backgrounding', + '--disable-default-apps', + '--no-first-run', + ], + defaultViewport: { + width: 1920, + height: 1080, + }, + }; +} + +/** Launch browser, ensuring the extension is loaded */ +async function launchBrowserWithExtension(): Promise { + const options = buildLaunchOptions(); + + // Debug logging + console.log('🔍 ========== BROWSER LAUNCH DEBUG =========='); + console.log('🔍 Current working directory:', process.cwd()); + console.log('🔍 Extension path (relative):', EXTENSION_PATH); + console.log('🔍 Extension path (absolute):', require('path').resolve(EXTENSION_PATH)); + + // Docker-specific environment debugging + console.log('🔍 ========== ENVIRONMENT DEBUG =========='); + console.log('🔍 NODE_ENV:', process.env.NODE_ENV); + console.log('🔍 Platform:', process.platform); + console.log('🔍 Architecture:', process.arch); + console.log('🔍 User ID:', process.getuid?.() || 'N/A'); + console.log('🔍 Group ID:', process.getgid?.() || 'N/A'); + console.log('🔍 Chrome executable path:', process.env.PUPPETEER_EXECUTABLE_PATH || 'using bundled Chrome'); + console.log('🔍 Display environment:', process.env.DISPLAY || 'Not set'); + + // Check if extension directory exists and list contents + const fs = require('fs'); + const extensionPath = require('path').resolve(EXTENSION_PATH); + console.log('🔍 ========== FILE SYSTEM DEBUG =========='); + console.log('🔍 Extension directory exists:', fs.existsSync(extensionPath)); + + if (fs.existsSync(extensionPath)) { + console.log('🔍 Extension directory contents:', fs.readdirSync(extensionPath)); + + // Check file permissions for each file + const files = fs.readdirSync(extensionPath); + files.forEach((file: string) => { + const filePath = require('path').join(extensionPath, file); + const stats = fs.statSync(filePath); + console.log(`🔍 File ${file}:`, { + readable: fs.constants.R_OK, + exists: fs.existsSync(filePath), + size: stats.size, + mode: stats.mode.toString(8), + isFile: stats.isFile(), }); + }); + + // Check for manifest.json specifically + const manifestPath = require('path').join(extensionPath, 'manifest.json'); + console.log('🔍 Manifest file exists:', fs.existsSync(manifestPath)); + + if (fs.existsSync(manifestPath)) { + try { + const manifestContent = fs.readFileSync(manifestPath, 'utf8'); + console.log('🔍 Manifest file size:', manifestContent.length); + const manifest = JSON.parse(manifestContent); + console.log('🔍 Manifest content:', JSON.stringify(manifest, null, 2)); + console.log('🔍 Manifest key field:', manifest.key); + console.log('🔍 Manifest version:', manifest.manifest_version); + console.log('🔍 Background script:', manifest.background?.service_worker); + } catch (error) { + console.error('🔍 Error reading manifest:', error); + } } - - // Test Puppeteer endpoint - if (pathname === '/test-puppeteer') { + } else { + console.error('❌ Extension directory does not exist!'); + // Try to list parent directory + const parentDir = require('path').dirname(extensionPath); + console.log('🔍 Parent directory:', parentDir); + if (fs.existsSync(parentDir)) { + console.log('🔍 Parent directory contents:', fs.readdirSync(parentDir)); + } + } + + // Chrome executable validation + console.log('🔍 ========== CHROME EXECUTABLE DEBUG =========='); + const chromeExecutable = process.env.PUPPETEER_EXECUTABLE_PATH; + console.log('🔍 Chrome executable path:', chromeExecutable || 'using bundled Chrome/Chromium'); + console.log('🔍 Skip Chromium download:', process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD || 'false'); + + // Debug: Find available Chrome/Chromium executables in the container + console.log('🔍 Searching for available Chrome/Chromium executables...'); + const possiblePaths = [ + '/usr/bin/chromium', // Standard Chromium location + '/usr/bin/chromium-browser', // Alternative Chromium name + '/usr/bin/google-chrome-stable', + '/usr/bin/google-chrome', + '/opt/google/chrome/chrome', + '/usr/bin/chrome', + '/opt/chromium.org/chromium/chromium', // Chromium snap location + '/snap/bin/chromium', // Snap Chromium + '/usr/local/bin/chromium', // Local install + '/usr/local/bin/chrome', // Local install + ]; + + let foundChrome: string | null = null; + possiblePaths.forEach(path => { + const exists = fs.existsSync(path); + console.log(`🔍 ${path}: ${exists ? 'EXISTS' : 'NOT FOUND'}`); + if (exists && !foundChrome) { + foundChrome = path; + } + }); + + // Try to find any Chrome/Chromium executable using find command + try { + const { exec } = require('child_process'); + const { promisify } = require('util'); + const execAsync = promisify(exec); + + console.log('🔍 Searching filesystem for Chrome/Chromium executables...'); + const { stdout: findResults } = await execAsync('find /usr /opt /snap -name "*chromium*" -o -name "*chrome*" 2>/dev/null | head -20 || echo "find command failed"'); + console.log('🔍 Find results:', findResults.trim()); + + // Also check what's in common bin directories + const binDirs = ['/usr/bin', '/usr/local/bin', '/opt']; + for (const dir of binDirs) { + if (fs.existsSync(dir)) { + try { + const files = fs.readdirSync(dir).filter((f: string) => f.includes('chrome') || f.includes('chromium')); + if (files.length > 0) { + console.log(`🔍 Chrome/Chromium files in ${dir}:`, files); + } + } catch (error) { + console.log(`🔍 Could not read ${dir}:`, (error as Error).message); + } + } + } + + } catch (error) { + console.warn('🔍 Could not search filesystem:', (error as Error).message); + } + + // Try to find Chrome/Chromium via which command + try { + const { exec } = require('child_process'); + const { promisify } = require('util'); + const execAsync = promisify(exec); + const { stdout: whichChrome } = await execAsync('which chromium-browser || which chromium || which google-chrome-stable || which google-chrome || echo "not found"'); + console.log('🔍 Which Chrome/Chromium:', whichChrome.trim()); + + // Get version of found executable + if (whichChrome.trim() !== 'not found') { try { - console.log('Launching browser...'); - const browser = await puppeteer.launch({ - headless: "new", - executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-dev-shm-usage' - ] - }); + const { stdout: version } = await execAsync(`${whichChrome.trim()} --version`); + console.log('🔍 Found browser version:', version.trim()); + foundChrome = whichChrome.trim(); + } catch (versionError) { + console.warn('🔍 Could not get browser version:', (versionError as Error).message); + } + } + } catch (error) { + console.warn('🔍 Could not run which command:', (error as Error).message); + } + + // Check Puppeteer's expected Chrome location + try { + const puppeteer = require('puppeteer'); + console.log('🔍 Puppeteer version:', puppeteer._launcher?._preferredRevision || 'unknown'); + + // Try to get default executable path from Puppeteer + if (puppeteer.executablePath) { + const defaultPath = puppeteer.executablePath(); + console.log('🔍 Puppeteer default executable path:', defaultPath); + if (fs.existsSync(defaultPath)) { + console.log('🔍 Puppeteer default executable EXISTS'); + foundChrome = defaultPath; + } else { + console.log('🔍 Puppeteer default executable NOT FOUND'); + } + } + } catch (error) { + console.warn('🔍 Could not get Puppeteer executable path:', (error as Error).message); + } + + if (foundChrome) { + console.log('🔍 ✅ Using Chrome/Chromium at:', foundChrome); + } else { + console.log('🔍 ❌ No Chrome/Chromium executable found'); + } + + console.log('🔍 Browser launch options:', JSON.stringify(options, null, 2)); + + console.log('🚀 Launching browser with extension...'); + + // Progressive testing approach to isolate the issue + console.log('🔍 ========== PROGRESSIVE BROWSER TESTING =========='); + + // Test 0: Direct Chrome execution test + console.log('🧪 Test 0: Direct Chrome execution test...'); + try { + const { exec } = require('child_process'); + const { promisify } = require('util'); + const execAsync = promisify(exec); + + const chromePath = foundChrome || '/root/.cache/puppeteer/chrome/linux-140.0.7339.82/chrome-linux64/chrome'; + console.log('🔍 Testing Chrome binary directly:', chromePath); + + // Test Chrome version command + const { stdout: versionOutput } = await execAsync(`timeout 10s ${chromePath} --version 2>&1 || echo "Chrome version failed"`); + console.log('🔍 Chrome version output:', versionOutput.trim()); + + // Test Chrome with basic flags + const { stdout: helpOutput } = await execAsync(`timeout 5s ${chromePath} --help 2>&1 | head -5 || echo "Chrome help failed"`); + console.log('🔍 Chrome help output:', helpOutput.trim()); + + // Test Chrome startup with minimal flags + console.log('🔍 Testing Chrome startup with minimal flags...'); + const testCommand = `timeout 10s ${chromePath} --no-sandbox --disable-gpu --headless --disable-dev-shm-usage --remote-debugging-port=9223 --user-data-dir=/tmp/chrome-test --dump-dom about:blank 2>&1 || echo "Chrome startup failed"`; + const { stdout: startupOutput } = await execAsync(testCommand); + console.log('🔍 Chrome startup test:', startupOutput.includes('') ? 'SUCCESS - Chrome can start' : 'FAILED - Chrome cannot start'); + console.log('🔍 Chrome startup output:', startupOutput.substring(0, 200) + '...'); + + } catch (error) { + console.error('❌ Test 0 Chrome direct execution failed:', (error as Error).message); + } + + // Test 1: Basic browser launch (we know this works) + console.log('🧪 Test 1: Basic browser launch without extensions...'); + try { + console.log('🔍 Starting basic browser launch with new headless mode...'); + const testBrowser1 = await puppeteer.launch({ + headless: 'new' as any, // Use new headless mode + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-gpu', + ] + }); + console.log('✅ Test 1 PASSED: Basic browser launch works'); + + // Test that we can create a page + const page = await testBrowser1.newPage(); + await page.goto('data:text/html,

Test

'); + console.log('✅ Test 1.1 PASSED: Page creation and navigation works'); + + await testBrowser1.close(); + } catch (error) { + console.error('❌ Test 1 FAILED: Basic browser launch failed:', (error as Error).message); + console.error('❌ This indicates Chrome cannot start in this container environment'); + console.error('❌ Possible causes:'); + console.error(' - Platform architecture mismatch (AMD64 vs ARM64)'); + console.error(' - Missing container capabilities or permissions'); + console.error(' - Chrome binary compatibility issues'); + throw error; + } + + // Test 2: Browser launch with extension flags but no actual extension + console.log('🧪 Test 2: Browser launch with extension flags (no extension)...'); + try { + const testBrowser2 = await puppeteer.launch({ + headless: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-extensions-except=/nonexistent/path', + '--load-extension=/nonexistent/path', + ] + }); + console.log('✅ Test 2 PASSED: Extension flags work (even with invalid path)'); + await testBrowser2.close(); + } catch (error) { + console.error('❌ Test 2 FAILED: Extension flags cause issues:', (error as Error).message); + console.log('🔍 This suggests extension flags themselves are problematic'); + } + + // Test 3: Browser launch with valid extension path + console.log('🧪 Test 3: Browser launch with actual extension...'); + + // Add extra logging around the launch process + try { + const browserInstance = await puppeteer.launch(options); + console.log('✅ Browser launched successfully'); + + // Get browser version immediately + try { + const version = await browserInstance.version(); + console.log('✅ Browser version:', version); + } catch (versionError) { + console.warn('⚠️ Could not get browser version:', (versionError as Error).message); + } + + // Log initial targets immediately after launch + console.log('🔍 ========== INITIAL TARGETS DEBUG =========='); + const initialTargets = browserInstance.targets(); + console.log('🔍 Initial target count:', initialTargets.length); + initialTargets.forEach((target, index) => { + console.log(`🔍 Target ${index}:`, { + type: target.type(), + url: target.url(), + isServiceWorker: target.type() === 'service_worker', + isExtensionUrl: target.url().startsWith('chrome-extension://'), + isBackgroundPage: target.type() === 'background_page', + opener: target.opener()?.url() || 'none', + }); + }); + + // Wait a bit for extensions to load and check again + console.log('🔍 Waiting 3 seconds for extensions to initialize...'); + await new Promise(resolve => setTimeout(resolve, 3000)); + + const postWaitTargets = browserInstance.targets(); + console.log('🔍 ========== POST-WAIT TARGETS DEBUG =========='); + console.log('🔍 Target count after wait:', postWaitTargets.length); + postWaitTargets.forEach((target, index) => { + console.log(`🔍 Post-Wait Target ${index}:`, { + type: target.type(), + url: target.url(), + isServiceWorker: target.type() === 'service_worker', + isExtensionUrl: target.url().startsWith('chrome-extension://'), + isBackgroundPage: target.type() === 'background_page', + endsWithBackgroundJs: target.url().endsWith('background.js'), + containsStreaming: target.url().includes('streaming'), + }); + }); + + // Try to access chrome://extensions/ page for additional debugging + try { + console.log('🔍 ========== CHROME EXTENSIONS PAGE DEBUG =========='); + const debugPage = await browserInstance.newPage(); + await debugPage.goto('chrome://extensions/', { waitUntil: 'domcontentloaded', timeout: 5000 }); + + // Try to extract extension information from the page + const extensionInfo = await debugPage.evaluate(() => { + const extensions = Array.from(document.querySelectorAll('extensions-item')); + return extensions.map(ext => ({ + id: ext.getAttribute('id'), + name: ext.querySelector('#name')?.textContent?.trim(), + enabled: !ext.hasAttribute('disabled'), + version: ext.querySelector('#version')?.textContent?.trim(), + })); + }); + + console.log('🔍 Extensions found on chrome://extensions/:', extensionInfo); + await debugPage.close(); + } catch (extensionsPageError) { + console.warn('⚠️ Could not access chrome://extensions/ page:', (extensionsPageError as Error).message); + } + + return browserInstance; + + } catch (launchError) { + console.error('❌ Browser launch failed:', launchError); + console.error('❌ Launch error details:', { + name: (launchError as Error).name, + message: (launchError as Error).message, + stack: (launchError as Error).stack?.split('\n').slice(0, 5), + }); + throw launchError; + } +} + +/** Wait for the extension service worker and get streaming page */ +async function getExtensionStreamingPage(browser: Browser, timeout = 15000): Promise { + console.log('🔍 ========== EXTENSION SERVICE WORKER DETECTION =========='); + console.log('🔍 Looking for extension service worker...'); + console.log('🔍 Timeout set to:', timeout, 'ms'); + + // Log all current targets before waiting + const preTargets = browser.targets(); + console.log('🔍 Current targets before waiting:', preTargets.length); + preTargets.forEach((target, index) => { + console.log(`🔍 Pre-Target ${index}:`, { + type: target.type(), + url: target.url(), + isServiceWorker: target.type() === 'service_worker', + isExtensionUrl: target.url().startsWith('chrome-extension://'), + endsWithBackgroundJs: target.url().endsWith('background.js'), + }); + }); + + try { + console.log('🔍 Starting waitForTarget for service worker...'); + + // Wait for the service worker from our extension (MV3) + const workerTarget = await browser.waitForTarget( + target => { + const isServiceWorker = target.type() === 'service_worker'; + const endsWithBackgroundJs = target.url().endsWith('background.js'); + const isMatch = isServiceWorker && endsWithBackgroundJs; - const version = await browser.version(); - const wsEndpoint = browser.wsEndpoint(); + console.log(`🔍 Evaluating target: ${target.url()}`); + console.log(`🔍 - Type: ${target.type()} (isServiceWorker: ${isServiceWorker})`); + console.log(`🔍 - Ends with background.js: ${endsWithBackgroundJs}`); + console.log(`🔍 - Match: ${isMatch}`); - await browser.close(); + return isMatch; + }, + { timeout } + ); + + console.log('✅ Found extension service worker:', workerTarget.url()); + + // Try to get the worker + let worker; + try { + worker = await workerTarget.worker(); + console.log('✅ Got worker object:', !!worker); + } catch (workerError) { + console.warn('⚠️ Could not get worker object (this might be normal):', (workerError as Error).message); + } + + // Get the extension ID from the worker URL + const urlMatch = workerTarget.url().match(/chrome-extension:\/\/([^\/]+)/); + const extensionId = urlMatch?.[1]; + + console.log('🔍 URL match result:', urlMatch); + console.log('🔍 Extracted extension ID:', extensionId); + + if (!extensionId) { + throw new Error('Could not extract extension ID from worker URL: ' + workerTarget.url()); + } + + console.log('✅ Detected extension ID:', extensionId); + EXTENSION_ID = extensionId; + + // Wait for the streaming.html page to be created by background.js + const streamingUrl = `chrome-extension://${extensionId}/streaming.html`; + console.log('🔍 ========== STREAMING PAGE DETECTION =========='); + console.log('🔍 Waiting for streaming page:', streamingUrl); + console.log('🔍 Timeout set to:', timeout, 'ms'); + + // Log current targets before waiting for streaming page + const preStreamingTargets = browser.targets(); + console.log('🔍 Current targets before waiting for streaming page:', preStreamingTargets.length); + preStreamingTargets.forEach((target, index) => { + console.log(`🔍 Pre-Streaming Target ${index}:`, { + type: target.type(), + url: target.url(), + isPage: target.type() === 'page', + isStreamingUrl: target.url() === streamingUrl, + }); + }); + + const streamingTarget = await browser.waitForTarget( + target => { + const isPage = target.type() === 'page'; + const isStreamingUrl = target.url() === streamingUrl; + const isMatch = isPage && isStreamingUrl; - return new Response(JSON.stringify({ - status: 'success', - browserVersion: version, - wsEndpoint: wsEndpoint - }), { - headers: { 'Content-Type': 'application/json' } - }); - } catch (error: any) { - console.error('Puppeteer test failed:', error); - return new Response(JSON.stringify({ - status: 'error', - message: error.message || 'Unknown error', - stack: error.stack || '' - }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }); + console.log(`🔍 Evaluating streaming target: ${target.url()}`); + console.log(`🔍 - Type: ${target.type()} (isPage: ${isPage})`); + console.log(`🔍 - URL matches: ${isStreamingUrl}`); + console.log(`🔍 - Match: ${isMatch}`); + + return isMatch; + }, + { timeout } + ); + + console.log('✅ Found streaming page target:', streamingTarget.url()); + + const page = await streamingTarget.page(); + if (!page) { + throw new Error('Failed to get page from streaming target'); + } + + console.log('✅ Got streaming page object successfully'); + console.log('✅ Final streaming page URL:', page.url()); + + return page; + + } catch (error) { + console.error('❌ Failed to get extension streaming page:', error); + + // Enhanced debug: show all available targets at the time of failure + console.log('🔍 ========== FAILURE DEBUG - ALL TARGETS =========='); + const targets = browser.targets(); + console.log('🔍 Total targets at failure:', targets.length); + targets.forEach((target, index) => { + console.log(`🔍 Failure Target ${index}:`, { + type: target.type(), + url: target.url(), + isServiceWorker: target.type() === 'service_worker', + isPage: target.type() === 'page', + isExtensionUrl: target.url().startsWith('chrome-extension://'), + endsWithBackgroundJs: target.url().endsWith('background.js'), + containsStreaming: target.url().includes('streaming'), + }); + }); + + throw error; + } +} + +/** Check if INITIALIZE function exists in the page */ +async function assertExtensionLoaded(page: Page, maxRetries = 3) { + const wait = (ms: number) => new Promise(res => setTimeout(res, ms)); + + for (let attempt = 0; attempt < maxRetries; attempt++) { + try { + const hasInitialize = await page.evaluate(() => typeof (globalThis as any).INITIALIZE === 'function'); + if (hasInitialize) { + console.log('INITIALIZE function found in extension page'); + return; + } + } catch (error) { + console.log(`Attempt ${attempt + 1}: INITIALIZE not ready yet`); + } + await wait(Math.pow(100, attempt)); // 100ms, 1s, 10s + } + throw new Error('Could not find INITIALIZE function in the browser context after retries'); +} + +/** Start monitoring active connections via Puppeteer polling */ +async function startConnectionMonitoring() { + if (!streamingPage) { + console.warn('Cannot start connection monitoring: no active streaming page'); + return; + } + + console.log('Starting connection monitoring...'); + + connectionCheckInterval = setInterval(async () => { + try { + if (!streamingPage) { + console.log('Streaming page no longer available, stopping monitoring'); + stopConnectionMonitoring(); + return; + } + + const activeCount = await streamingPage.evaluate(() => { + return window.activeConnections ? window.activeConnections.size : 0; + }); + + // Enhanced debugging - show what's actually in the set + const activeConnectionsDebug = await streamingPage.evaluate(() => { + if (!window.activeConnections) return { size: 0, connections: [] }; + return { + size: window.activeConnections.size, + connections: Array.from(window.activeConnections) + }; + }); + + // Check streaming debug info + const streamingDebug = await streamingPage.evaluate(() => { + return window.streamingDebug || { debug: 'not available' }; + }); + + // Get more detailed extension state + const extensionState = await streamingPage.evaluate(() => { + return { + hasStreamingDebug: typeof window.streamingDebug !== 'undefined', + hasActiveConnections: typeof window.activeConnections !== 'undefined', + hasInitializeFunction: typeof window.INITIALIZE === 'function', + windowKeys: Object.keys(window).filter(key => key.includes('streaming') || key.includes('active') || key.includes('INITIALIZE')), + location: window.location.href, + userAgent: navigator.userAgent, + timestamp: new Date().toISOString() + }; + }); + + console.log(`Active connections: ${activeCount}`); + console.log(`Connection details:`, activeConnectionsDebug); + console.log(`Streaming debug:`, streamingDebug); + console.log(`Extension state:`, extensionState); + + if (activeCount === 0 && !shutdownTimer) { + console.log(`No active connections, starting ${GRACE_PERIOD_MS}ms shutdown timer...`); + shutdownTimer = setTimeout(() => { + console.log('Grace period expired, shutting down browser...'); + shutdownBrowser(); + }, GRACE_PERIOD_MS); + } else if (activeCount > 0 && shutdownTimer) { + console.log('Active connections detected, cancelling shutdown timer'); + clearTimeout(shutdownTimer); + shutdownTimer = null; } + } catch (error) { + console.error('Failed to check connection status:', error); + // Continue monitoring even if one check fails } + }, POLL_INTERVAL_MS); +} + +/** Stop connection monitoring */ +function stopConnectionMonitoring() { + if (connectionCheckInterval) { + clearInterval(connectionCheckInterval); + connectionCheckInterval = null; + console.log('Connection monitoring stopped'); + } + + if (shutdownTimer) { + clearTimeout(shutdownTimer); + shutdownTimer = null; + console.log('Shutdown timer cancelled'); + } +} + +/** Gracefully shutdown the browser and clean up resources */ +async function shutdownBrowser() { + console.log('Shutting down browser...'); + + stopConnectionMonitoring(); + + if (browser) { + try { + await browser.close(); + console.log('Browser closed successfully'); + } catch (error) { + console.error('Error closing browser:', error); + } + browser = undefined; + activePage = undefined; + streamingPage = undefined; + EXTENSION_ID = null; + } +} + +/* ---------- Route Handlers ---------- */ +async function handleHealth(): Promise { + return jsonResponse({ + status: 'healthy', + puppeteer: 'imported', + location: process.env.CLOUDFLARE_LOCATION || 'local', + region: process.env.CLOUDFLARE_REGION || 'dev', + expectedExtensionId: EXTENSION_ID || 'unknown', + browserActive: !!browser, + monitoringActive: !!connectionCheckInterval, + }); +} + +async function handlePing(): Promise { + return jsonResponse({ + status: 'pong', + timestamp: Date.now(), + browserActive: !!browser, + }); +} + +async function handleTest(): Promise { + let testBrowser: Browser | undefined; + try { + testBrowser = await launchBrowserWithExtension(); + const version = await testBrowser.version(); + + // Get extension streaming page + streamingPage = await getExtensionStreamingPage(testBrowser); + + await testBrowser.close(); + return jsonResponse({ + status: 'success', + browserVersion: version, + extensionFound: true, + extensionId: EXTENSION_ID, + }); + } catch (err: any) { + console.error('handleTest error:', err); + if (testBrowser) await testBrowser.close(); + return jsonResponse({ status: 'error', message: err.message }, { status: 500 }); + } +} + +async function handleDebugState(traceId: string): Promise { + const snapshot = await collectBrowserState(traceId); + return jsonResponse(snapshot, { + headers: buildTraceHeaders(traceId), + }); +} + +async function handleStartStream( + data: { url: string; peerId: string; iceServers?: IceServerConfig[] }, + traceId: string +): Promise { + try { + const { url: targetUrl, peerId: destPeerId, iceServers } = data; + if (!targetUrl || !destPeerId) { + return jsonResponse( + { error: 'Missing targetUrl or peerId', traceId }, + { status: 400, headers: buildTraceHeaders(traceId) } + ); + } + + logTrace(traceId, 'start_stream_request_received', { + targetUrl, + destPeerId, + iceServerCount: Array.isArray(iceServers) ? iceServers.length : 0, + browserReused: !!browser, + }); + + // Use existing browser or launch new one + if (!browser) { + logTrace(traceId, 'browser_launch_start'); + browser = await launchBrowserWithExtension(); + logTrace(traceId, 'browser_launch_complete'); + } else { + logTrace(traceId, 'browser_reuse'); + } + + // Create new page for this stream + const page = await browser.newPage(); + activePage = page; // Set as active page for monitoring + page.on('console', (msg) => { + logTrace(traceId, `page_console_${msg.type()}`, { text: msg.text() }); + }); + page.on('pageerror', (error) => { + logTrace(traceId, 'page_error', { message: error.message }); + }); + + // Navigate to target URL + logTrace(traceId, 'page_navigation_start', { targetUrl }); + await page.goto(targetUrl); + logTrace(traceId, 'page_navigation_complete', { + finalUrl: page.url(), + title: await page.title().catch(() => '(unavailable)'), + }); + + // Set page to full screen + await page.setViewport({ width: 1920, height: 1080 }); // Set a large viewport + logTrace(traceId, 'page_viewport_set', { width: 1920, height: 1080 }); + + // Get extension streaming page and initialize streaming + logTrace(traceId, 'extension_page_wait_start'); + streamingPage = await getExtensionStreamingPage(browser, 30000); // This also sets EXTENSION_ID + logTrace(traceId, 'extension_page_ready', { extensionId: EXTENSION_ID }); + + // Trigger a user-gesture-like command to satisfy activeTab requirements + try { + const pageTargets = browser.targets().filter(t => t.type() === 'page'); + const nyTimesTarget = pageTargets.find(t => t.url().startsWith('https://')) || pageTargets[0]; + const nyPage = await nyTimesTarget?.page(); + if (nyPage) { + // Simulate the keyboard shortcut for the command (Alt+Shift+S) + await nyPage.keyboard.down('Alt'); + await nyPage.keyboard.down('Shift'); + await nyPage.keyboard.press('KeyS'); + await nyPage.keyboard.up('Shift'); + await nyPage.keyboard.up('Alt'); + logTrace(traceId, 'capture_shortcut_sent'); + } + } catch (e) { + logTrace(traceId, 'capture_shortcut_failed', { message: (e as Error).message }); + } + + // Set up console log monitoring for the extension page + streamingPage.on('console', (msg) => { + const type = msg.type(); + const text = msg.text(); + logTrace(traceId, `extension_console_${type}`, { text }); + }); + + streamingPage.on('pageerror', (error) => { + logTrace(traceId, 'extension_page_error', { message: error.message }); + }); + + // Test if we can execute code in the extension context + console.log('🧪 Testing extension context execution...'); + try { + const testResult = await streamingPage.evaluate(() => { + console.log('[TEST] This is a test log from extension context'); + return { + location: window.location.href, + hasWindow: typeof window !== 'undefined', + hasPeer: typeof (globalThis as any).Peer !== 'undefined', + hasChrome: typeof (globalThis as any).chrome !== 'undefined', + hasTabCapture: typeof (globalThis as any).chrome !== 'undefined' && typeof (globalThis as any).chrome.tabCapture !== 'undefined', + windowKeys: Object.keys(window).filter(key => + key.includes('streaming') || + key.includes('active') || + key.includes('INITIALIZE') || + key.includes('Peer') + ) + }; + }); + logTrace(traceId, 'extension_context_test', testResult as Record); + } catch (error) { + logTrace(traceId, 'extension_context_test_failed', { message: (error as Error).message }); + } + + // Ensure INITIALIZE function is loaded + await assertExtensionLoaded(streamingPage); + logTrace(traceId, 'extension_initialize_detected'); + + // Force a simple log to test console monitoring + logTrace(traceId, 'extension_console_probe_start'); + await streamingPage.evaluate(() => { + console.log('[FORCED-TEST] This should appear in container logs if console monitoring works'); + console.error('[FORCED-ERROR] This is a test error'); + console.warn('[FORCED-WARN] This is a test warning'); + }); + + logTrace(traceId, 'extension_console_probe_complete'); + + const srcPeerId = crypto.randomUUID(); + const peers = { srcPeerId, destPeerId, iceServers: Array.isArray(iceServers) ? iceServers : [] }; + + // Initialize streaming in extension page + logTrace(traceId, 'extension_initialize_start', { + srcPeerId, + destPeerId, + extensionUrl: streamingPage.url(), + }); + + try { + await Promise.race([ + streamingPage.evaluate(async (p: any) => { + console.log('[PUPPETEER] INITIALIZE call starting with params:', p); + // @ts-ignore + const result = await INITIALIZE(p); + console.log('[PUPPETEER] INITIALIZE call completed, result:', result); + return result; + }, peers), + new Promise((_, reject) => setTimeout(() => reject(new Error('INITIALIZE timeout')), 30000)), + ]); + + logTrace(traceId, 'extension_initialize_complete'); + + // Check the state immediately after INITIALIZE + const postInitState = await streamingPage.evaluate(() => { + return { + activeConnections: window.activeConnections ? Array.from(window.activeConnections) : null, + activeConnectionsSize: window.activeConnections ? window.activeConnections.size : 0, + streamingDebug: window.streamingDebug || null, + hasInitialize: typeof window.INITIALIZE === 'function' + }; + }); + + logTrace(traceId, 'post_initialize_state', postInitState as Record); + await new Promise((resolve) => setTimeout(resolve, 1500)); + await collectBrowserState(traceId); + + } catch (error) { + logTrace(traceId, 'extension_initialize_failed', { + name: (error as Error).name, + message: (error as Error).message, + stack: (error as Error).stack + }); + throw error; // Re-throw to propagate the error + } + + // Start connection monitoring if not already running + if (!connectionCheckInterval) { + startConnectionMonitoring(); + } + + logTrace(traceId, 'start_stream_response_sent', { + srcPeerId, + monitoringActive: !!connectionCheckInterval, + }); + return jsonResponse({ + status: 'success', + traceId, + srcPeerId, + browserWSEndpoint: browser.wsEndpoint(), + monitoringActive: !!connectionCheckInterval + }, { + headers: buildTraceHeaders(traceId), + }); + } catch (err: any) { + logTrace(traceId, 'start_stream_error', { + message: err.message, + stack: err.stack, + }); + // Don't close browser on error - let monitoring handle lifecycle + return jsonResponse( + { status: 'error', message: err.message, traceId }, + { status: 500, headers: buildTraceHeaders(traceId) } + ); + } +} + +/* ---------- Node.js HTTP Server ---------- */ +const server = http.createServer(async (req: any, res: any) => { + const parsedUrl = url.parse(req.url || '', true); + const { pathname } = parsedUrl; + const traceId = req.headers['x-stream-trace-id'] || crypto.randomUUID(); + + try { + logTrace(traceId, 'http_request_received', { + method: req.method, + pathname, + }); + // Set CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, x-stream-trace-id'); + res.setHeader('x-stream-trace-id', traceId); + + // Handle CORS preflight requests + if (req.method === 'OPTIONS') { + res.writeHead(200); + res.end(); + return; + } + + let response: Response; + + if (pathname === '/health') { + response = await handleHealth(); + } else if (pathname === '/ping') { + response = await handlePing(); + } else if (pathname === '/test-puppeteer') { + response = await handleTest(); + } else if (pathname === '/debug-state') { + response = await handleDebugState(traceId); + } else if (pathname === '/start-stream' && req.method === 'POST') { + // Parse request body for POST requests + let body = ''; + req.on('data', (chunk: any) => { + body += chunk.toString(); + }); + + await new Promise((resolve) => { + req.on('end', resolve); + }); + + const data = parseStartStreamRequest(JSON.parse(body)); + response = await handleStartStream(data, traceId); + } else { + response = new Response(`Not Found: ${req.method} ${req.url} (parsed path: ${pathname})`, { status: 404 }); + } + + // Convert Response object to Node.js response + res.writeHead(response.status || 200, { + 'Content-Type': response.headers.get('Content-Type') || 'application/json', + ...Object.fromEntries(response.headers.entries()) + }); + + const responseText = await response.text(); + res.end(responseText); + + } catch (err: any) { + if (err instanceof SyntaxError || err?.message?.includes('must be')) { + res.writeHead(400, { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, x-stream-trace-id', + 'x-stream-trace-id': req.headers['x-stream-trace-id'] || '', + }); + res.end(JSON.stringify({ status: 'error', message: err.message })); + return; + } + + console.error('Unhandled error:', err); + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } +}); + +const PORT = 8080; +server.listen(PORT, '0.0.0.0', () => { + console.log(`Container server running at http://0.0.0.0:${PORT}`); +}); - return new Response('Not Found', { status: 404 }); - }, +// Graceful shutdown on process termination +process.on('SIGTERM', async () => { + console.log('Received SIGTERM, shutting down gracefully...'); + await shutdownBrowser(); + process.exit(0); }); -console.log(`Container server running at http://localhost:${server.port}`); \ No newline at end of file +process.on('SIGINT', async () => { + console.log('Received SIGINT, shutting down gracefully...'); + await shutdownBrowser(); + process.exit(0); +}); diff --git a/examples/bun-stream-server/package.json b/examples/bun-stream-server/package.json index 9fce5d3..d5cc445 100644 --- a/examples/bun-stream-server/package.json +++ b/examples/bun-stream-server/package.json @@ -5,11 +5,14 @@ "scripts": { "deploy": "wrangler deploy", "dev": "wrangler dev", - "start": "wrangler dev" + "start": "wrangler dev", + "test": "npx --yes vitest@2.1.9 run test/index.test.ts", + "test:e2e": "playwright test" }, "devDependencies": { - "@types/bun": "^1.2.10", - "@types/node": "^22.14.1", - "wrangler": "https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-wrangler-8586" + "@playwright/test": "^1.52.0", + "@types/bun": "^1.2.21", + "@types/node": "^22.18.1", + "wrangler": "4.73.0" } } diff --git a/examples/bun-stream-server/playwright.config.ts b/examples/bun-stream-server/playwright.config.ts new file mode 100644 index 0000000..9c8fb7d --- /dev/null +++ b/examples/bun-stream-server/playwright.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: "./test", + testMatch: /e2e\.spec\.ts/, + timeout: 180_000, + expect: { + timeout: 30_000, + }, + retries: process.env.CI ? 1 : 0, + reporter: process.env.CI ? [["github"], ["html", { open: "never" }]] : "list", +}); diff --git a/examples/bun-stream-server/receiver.html b/examples/bun-stream-server/receiver.html new file mode 100644 index 0000000..ecce6e5 --- /dev/null +++ b/examples/bun-stream-server/receiver.html @@ -0,0 +1,792 @@ + + + + + + Stream Kit - Video Receiver + + + +
+

🎥 Stream Kit Video Receiver

+

Connect to a Puppeteer-launched stream to receive video

+ +
+

How to use:

+
    +
  1. Start the container server: cd container && bun run src/server.ts
  2. +
  3. Enter a URL below that you want to stream
  4. +
  5. Click Start Stream & Listen to begin!
  6. +
  7. The page will automatically start the stream and connect you
  8. +
  9. You'll see the live content from the Puppeteer browser!
  10. +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + + + + +
+ Ready - Enter a URL above and click "🚀 Start Stream & Listen" to begin +
+ + +
+ + + + + + + diff --git a/examples/bun-stream-server/scripts/cleanup-preview.mjs b/examples/bun-stream-server/scripts/cleanup-preview.mjs new file mode 100644 index 0000000..e8b950a --- /dev/null +++ b/examples/bun-stream-server/scripts/cleanup-preview.mjs @@ -0,0 +1,62 @@ +import { execFileSync } from "node:child_process"; +import path from "node:path"; + +const workerName = process.env.STREAM_KIT_WORKER_NAME; +const containerName = process.env.STREAM_KIT_CONTAINER_NAME; +const configPath = process.env.STREAM_KIT_CONFIG_PATH; + +if (!workerName || !containerName || !configPath) { + throw new Error( + "STREAM_KIT_WORKER_NAME, STREAM_KIT_CONTAINER_NAME, and STREAM_KIT_CONFIG_PATH are required" + ); +} + +function runWrangler(args, options = {}) { + return execFileSync("npx", ["wrangler", ...args], { + cwd: path.dirname(configPath), + encoding: "utf8", + stdio: ["pipe", "pipe", "pipe"], + ...options, + }); +} + +function parseJsonFromOutput(output) { + const match = output.match(/(\[\s*\{[\s\S]*\])\s*$/); + if (!match) { + throw new Error(`Could not find JSON payload in Wrangler output:\n${output}`); + } + + return JSON.parse(match[1]); +} + +try { + runWrangler(["delete", workerName, "--config", configPath, "--force"]); + console.log(`Deleted worker ${workerName}`); +} catch (error) { + const output = `${error.stdout || ""}${error.stderr || ""}`; + if ( + output.includes("There is currently no Worker published") || + output.includes("was not found") || + output.includes("No worker named") || + output.includes("This Worker does not exist on this account") + ) { + console.log(`Worker ${workerName} did not exist; skipping delete`); + } else { + throw error; + } +} + +const containers = parseJsonFromOutput( + runWrangler(["containers", "list", "--config", configPath]) +); +const matchingContainer = containers.find((container) => container.name === containerName); + +if (!matchingContainer) { + console.log(`Container ${containerName} did not exist; skipping delete`); + process.exit(0); +} + +runWrangler(["containers", "delete", matchingContainer.id, "--config", configPath], { + input: "y\n", +}); +console.log(`Deleted container ${containerName} (${matchingContainer.id})`); diff --git a/examples/bun-stream-server/scripts/render-wrangler-config.mjs b/examples/bun-stream-server/scripts/render-wrangler-config.mjs new file mode 100644 index 0000000..04e7aa8 --- /dev/null +++ b/examples/bun-stream-server/scripts/render-wrangler-config.mjs @@ -0,0 +1,65 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(scriptDir, ".."); + +const workerName = process.env.STREAM_KIT_WORKER_NAME || "bun-stream-server"; +const containerName = + process.env.STREAM_KIT_CONTAINER_NAME || "codeflare-containers"; +const outputPath = + process.env.STREAM_KIT_CONFIG_PATH || + path.resolve(projectRoot, ".wrangler/preview-wrangler.json"); +const configDir = path.dirname(outputPath); +const mainPath = path.relative(configDir, path.join(projectRoot, "src/index.ts")); +const dockerfilePath = path.relative( + configDir, + path.join(projectRoot, "container/Dockerfile") +); +const imagePath = process.env.STREAM_KIT_CONTAINER_IMAGE || dockerfilePath; + +const config = { + name: workerName, + main: mainPath, + compatibility_date: "2025-04-10", + compatibility_flags: [ + "nodejs_compat", + "nodejs_compat_populate_process_env", + ], + containers: [ + { + name: containerName, + image: imagePath, + class_name: "StreamContainer", + max_instances: 2, + }, + ], + durable_objects: { + bindings: [ + { + name: "STREAM_CONTAINER", + class_name: "StreamContainer", + }, + ], + }, + migrations: [ + { + tag: "v1", + new_sqlite_classes: ["MyContainer"], + }, + { + tag: "v2", + new_sqlite_classes: ["StreamContainer"], + deleted_classes: ["MyContainer"], + }, + ], + observability: { + enabled: true, + head_sampling_rate: 1, + }, +}; + +fs.mkdirSync(path.dirname(outputPath), { recursive: true }); +fs.writeFileSync(outputPath, `${JSON.stringify(config, null, 2)}\n`, "utf8"); +console.log(outputPath); diff --git a/examples/bun-stream-server/src/index.ts b/examples/bun-stream-server/src/index.ts index 96dcb56..81563b3 100644 --- a/examples/bun-stream-server/src/index.ts +++ b/examples/bun-stream-server/src/index.ts @@ -1,10 +1,156 @@ +import crypto from "crypto"; +import { Buffer } from "buffer"; +import { + parseTurnCredentialsResponse, + type IceServerConfig, +} from "./protocol"; + const OPEN_CONTAINER_PORT = 8080; +const STREAM_INSTANCE_NAME = "default-singleton-debug-v3"; +const TURN_TTL_SECONDS = 300; +const DEBUG_TOKEN_HEADER = "x-debug-token"; +const SESSION_ID_HEADER = "x-stream-session-id"; + +function logTrace(traceId: string, event: string, details?: Record) { + if (details) { + console.log(`[trace:${traceId}] ${event}`, details); + return; + } + console.log(`[trace:${traceId}] ${event}`); +} + +function withTraceHeader(response: Response, traceId: string): Response { + const cloned = new Response(response.body, response); + cloned.headers.set("x-stream-trace-id", traceId); + return cloned; +} + +function withSessionHeader(response: Response, sessionId: string | null): Response { + if (!sessionId) { + return response; + } + + const cloned = new Response(response.body, response); + cloned.headers.set(SESSION_ID_HEADER, sessionId); + return cloned; +} + +export function normalizeIceServers(iceServers: IceServerConfig[]): IceServerConfig[] { + return iceServers.map((server) => { + const urls = Array.isArray(server.urls) ? server.urls : [server.urls]; + const filteredUrls = urls.filter((url) => { + const normalizedUrl = url.toLowerCase(); + return !( + normalizedUrl.includes(":53?") || + normalizedUrl.endsWith(":53") || + normalizedUrl.includes(":53#") || + normalizedUrl.includes(":53/") + ); + }); + return { + ...server, + urls: filteredUrls, + }; + }).filter((server) => { + const urls = Array.isArray(server.urls) ? server.urls : [server.urls]; + return urls.length > 0; + }); +} + +function timingSafeMatches(actual: string, expected: string): boolean { + const actualBytes = Buffer.from(actual); + const expectedBytes = Buffer.from(expected); + + if (actualBytes.length !== expectedBytes.length) { + return false; + } + + return crypto.timingSafeEqual(actualBytes, expectedBytes); +} + +export function isDebugRequestAuthorized(request: Request, env: Env): boolean { + if (!env.DEBUG_STATE_TOKEN) { + return true; + } + + const providedToken = request.headers.get(DEBUG_TOKEN_HEADER); + if (!providedToken) { + return false; + } + + return timingSafeMatches(providedToken, env.DEBUG_STATE_TOKEN); +} + +function buildCorsHeaders(methods: string): HeadersInit { + return { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": methods, + "Access-Control-Allow-Headers": `Content-Type, x-stream-trace-id, ${DEBUG_TOKEN_HEADER}, ${SESSION_ID_HEADER}`, + "Access-Control-Expose-Headers": `x-stream-trace-id, ${SESSION_ID_HEADER}`, + }; +} + +export function resolveSessionId(request: Request): string | null { + const providedSessionId = request.headers.get(SESSION_ID_HEADER); + if (!providedSessionId) { + return null; + } + + const normalizedSessionId = providedSessionId.trim(); + if (!/^[a-zA-Z0-9_-]{1,100}$/.test(normalizedSessionId)) { + return null; + } + + return normalizedSessionId; +} + +export async function generateTurnIceServers( + env: Env, + traceId: string +): Promise { + const apiToken = env.CLOUDFLARE_TURN_API_TOKEN; + const turnKeyId = env.CLOUDFLARE_TURN_KEY_ID; + + if (!apiToken || !turnKeyId) { + throw new Error("TURN credentials are not configured in Worker secrets"); + } + + logTrace(traceId, "turn_credentials_request_start", { ttlSeconds: TURN_TTL_SECONDS }); + const response = await fetch( + `https://rtc.live.cloudflare.com/v1/turn/keys/${turnKeyId}/credentials/generate-ice-servers`, + { + method: "POST", + headers: { + Authorization: `Bearer ${apiToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ ttl: TURN_TTL_SECONDS }), + } + ); + + const bodyText = await response.text(); + if (!response.ok) { + logTrace(traceId, "turn_credentials_request_failed", { + status: response.status, + body: bodyText, + }); + throw new Error(`TURN credentials request failed: ${response.status}`); + } + + const parsed = parseTurnCredentialsResponse(JSON.parse(bodyText)); + const iceServers = normalizeIceServers(parsed.iceServers); + logTrace(traceId, "turn_credentials_request_complete", { + serverCount: iceServers.length, + }); + return iceServers; +} // Helper functions async function startAndWaitForPort( container: Container, portToAwait: number, - maxTries = 10 + traceId: string, + maxTries = 120 ) { const port = container.getTcpPort(portToAwait); let monitor; @@ -12,20 +158,40 @@ async function startAndWaitForPort( for (let i = 0; i < maxTries; i++) { try { if (!container.running) { - container.start(); + // @ts-ignore - enableInternet is required for Puppeteer to reach external websites + container.start({ enableInternet: true }); monitor = container.monitor(); + logTrace(traceId, "container_start_requested", { try: i + 1, port: portToAwait }); + } + + const res = await port.fetch("http://localhost:8080/ping", { + // @ts-ignore + signal: AbortSignal.timeout(1500), + headers: { + "x-stream-trace-id": traceId, + }, + }); + const text = await res.text(); + if (!res.ok) { + throw new Error(`fetch failed: ${res.status} ${text}`); } - await (await port.fetch("http://ping")).text(); + logTrace(traceId, "container_ready", { try: i + 1, port: portToAwait }); return; } catch (err: any) { - console.error("Error connecting to the container on", i, "try", err); + logTrace(traceId, "container_wait_retry", { + try: i + 1, + message: err.message, + name: err.name, + }); + const errMsg = (err.message || "").toLowerCase(); if ( - err.message.includes("listening") || - err.message.includes( - "there is no container instance that can be provided" - ) + errMsg.includes("listening") || + errMsg.includes("there is no container instance that can be provided") || + errMsg.includes("timeout") || + err.name === "TimeoutError" || + errMsg.includes("fetch failed") ) { - await new Promise((res) => setTimeout(res, 300)); + await new Promise((res) => setTimeout(res, 500)); continue; } throw err; @@ -36,25 +202,41 @@ async function startAndWaitForPort( ); } +async function waitForLocalhost(port: number, maxTries = 10) { + for (let i = 0; i < maxTries; i++) { + try { + await fetch(`http://localhost:${port}/ping`); + return; + } catch (err: any) { + console.error("Error connecting to localhost on", i, "try", err); + await new Promise((res) => setTimeout(res, 300)); + } + } + throw new Error( + `could not connect to localhost:${port} after ${maxTries} tries` + ); +} + async function proxyFetch( - container: any, + container: Container, request: Request, - portNumber: number + portNumber: number, + traceId: string ): Promise { + const headers = new Headers(request.headers); + headers.set("x-stream-trace-id", traceId); const response = await container .getTcpPort(portNumber) .fetch(request.url.replace("https://", "http://"), { method: request.method, - headers: request.headers, + headers, body: request.body, }); return response; } function createJsonResponse(data: unknown, init?: ResponseInit): Response { - // Convert data to string first const jsonString = JSON.stringify(data); - // Create response with explicit headers return new Response(jsonString, { ...init, headers: { @@ -65,34 +247,116 @@ function createJsonResponse(data: unknown, init?: ResponseInit): Response { } // Durable Object implementation -export class MyContainer implements DurableObject { +export class StreamContainer implements DurableObject { + private waitPromise: Promise | null = null; + constructor( private readonly ctx: DurableObjectState, private readonly env: Env - ) { - ctx.blockConcurrencyWhile(async () => { - const container = ctx.container; - if (!container) { - throw new Error("Container is not available"); + ) {} + + private async ensureRunning(traceId: string) { + if (this.ctx.container && !this.ctx.container.running && this.waitPromise) { + logTrace(traceId, "do_container_stopped_resetting_wait_promise"); + this.waitPromise = null; + } + + if (this.waitPromise) return this.waitPromise; + + this.waitPromise = (async () => { + if (!this.ctx.container) { + logTrace(traceId, "do_localhost_mode"); + await waitForLocalhost(OPEN_CONTAINER_PORT); + } else { + logTrace(traceId, "do_container_mode"); + await startAndWaitForPort(this.ctx.container, OPEN_CONTAINER_PORT, traceId); } - await startAndWaitForPort(container, OPEN_CONTAINER_PORT); - }); + })(); + + return this.waitPromise; } async fetch(request: Request): Promise { + const url = new URL(request.url); + const traceId = request.headers.get("x-stream-trace-id") || crypto.randomUUID(); + const startedAt = Date.now(); + logTrace(traceId, "do_request_received", { + method: request.method, + path: url.pathname, + }); + try { + await this.ensureRunning(traceId); + if (!this.ctx.container) { - throw new Error("Container is not available"); + // No container, proxy to localhost:8080 + logTrace(traceId, "do_proxy_local", { method: request.method, path: url.pathname }); + const localUrl = request.url.replace(new URL(request.url).origin, 'http://localhost:8080'); + const response = await fetch(localUrl, { + method: request.method, + headers: new Headers(request.headers), + body: request.body, + }); + const tracedResponse = withTraceHeader(response, traceId); + logTrace(traceId, "do_response_sent", { + status: tracedResponse.status, + durationMs: Date.now() - startedAt, + }); + return tracedResponse; + } else { + // Use the container + logTrace(traceId, "do_proxy_container", { method: request.method, path: url.pathname }); + const res = await proxyFetch( + this.ctx.container, + request, + OPEN_CONTAINER_PORT, + traceId + ); + const tracedResponse = withTraceHeader(res, traceId); + logTrace(traceId, "do_response_sent", { + status: tracedResponse.status, + durationMs: Date.now() - startedAt, + }); + return tracedResponse; } - return await proxyFetch( - this.ctx.container, - request, - OPEN_CONTAINER_PORT - ); } catch (error) { + if ( + this.ctx.container && + (error as Error).message.includes("container is not running") + ) { + logTrace(traceId, "do_retry_after_container_stopped"); + this.waitPromise = null; + await this.ensureRunning(traceId); + + const retriedResponse = await proxyFetch( + this.ctx.container, + request, + OPEN_CONTAINER_PORT, + traceId + ); + const tracedResponse = withTraceHeader(retriedResponse, traceId); + logTrace(traceId, "do_response_sent_after_retry", { + status: tracedResponse.status, + durationMs: Date.now() - startedAt, + }); + return tracedResponse; + } + + logTrace(traceId, "do_error", { + message: (error as Error).message, + durationMs: Date.now() - startedAt, + }); return createJsonResponse( - { error: "Container error: " + (error as Error).message }, - { status: 500 } + { + error: (!this.ctx.container ? "Local server" : "Container") + " error: " + (error as Error).message, + traceId, + }, + { + status: 500, + headers: { + "x-stream-trace-id": traceId, + }, + } ); } } @@ -101,28 +365,104 @@ export class MyContainer implements DurableObject { // Worker entry point export default { async fetch(request: Request, env: Env): Promise { + const traceId = request.headers.get("x-stream-trace-id") || crypto.randomUUID(); const url = new URL(request.url); - const pathname = url.pathname; - - if ( - pathname.startsWith("/stream") || - pathname === "/health" || - pathname === "/test-puppeteer" - ) { - const id = env.MY_CONTAINER.idFromName(pathname); - const stub = env.MY_CONTAINER.get(id); - return await stub.fetch(request); + const sessionId = resolveSessionId(request); + const streamInstanceName = sessionId ? `session-${sessionId}` : STREAM_INSTANCE_NAME; + logTrace(traceId, "worker_request_received", { + method: request.method, + path: url.pathname, + sessionId, + }); + + if (url.pathname === "/ice-servers" && request.method === "GET") { + try { + const response = withTraceHeader( + createJsonResponse( + { + iceServers: await generateTurnIceServers(env, traceId), + traceId, + sessionId, + }, + { + headers: { + ...buildCorsHeaders("GET, OPTIONS"), + }, + } + ), + traceId + ); + const tracedResponse = withSessionHeader(response, sessionId); + logTrace(traceId, "worker_response_sent", { status: tracedResponse.status, path: url.pathname, sessionId }); + return tracedResponse; + } catch (error) { + const response = withTraceHeader( + createJsonResponse( + { error: (error as Error).message, traceId, sessionId }, + { + status: 500, + headers: { + ...buildCorsHeaders("GET, OPTIONS"), + }, + } + ), + traceId + ); + const tracedResponse = withSessionHeader(response, sessionId); + logTrace(traceId, "worker_response_sent", { status: tracedResponse.status, path: url.pathname, sessionId }); + return tracedResponse; + } + } + + if (url.pathname === "/debug-state" && !isDebugRequestAuthorized(request, env)) { + const response = withTraceHeader( + createJsonResponse( + { error: "Forbidden", traceId }, + { + status: 403, + headers: buildCorsHeaders("GET, OPTIONS"), + } + ), + traceId + ); + const tracedResponse = withSessionHeader(response, sessionId); + logTrace(traceId, "worker_response_sent", { + status: tracedResponse.status, + path: url.pathname, + authorized: false, + sessionId, + }); + return tracedResponse; } - return createJsonResponse({ - message: "Stream Server Worker", - endpoints: [ - "GET /health", - "POST /stream", - "GET /stream/:id", - "DELETE /stream/:id", - "GET /test-puppeteer", - ], + if (request.method === "OPTIONS") { + const response = withTraceHeader( + new Response(null, { + status: 204, + headers: buildCorsHeaders("GET, POST, OPTIONS"), + }), + traceId + ); + const tracedResponse = withSessionHeader(response, sessionId); + logTrace(traceId, "worker_response_sent", { status: tracedResponse.status, path: url.pathname, sessionId }); + return tracedResponse; + } + + const id = env.STREAM_CONTAINER.idFromName(streamInstanceName); + const stub = env.STREAM_CONTAINER.get(id); + const forwardedRequest = new Request(request, { + headers: new Headers(request.headers), }); + forwardedRequest.headers.set("x-stream-trace-id", traceId); + if (sessionId) { + forwardedRequest.headers.set(SESSION_ID_HEADER, sessionId); + } + + const response = withSessionHeader( + withTraceHeader(await stub.fetch(forwardedRequest), traceId), + sessionId + ); + logTrace(traceId, "worker_response_sent", { status: response.status, path: url.pathname, sessionId }); + return response; }, }; diff --git a/examples/bun-stream-server/src/protocol.ts b/examples/bun-stream-server/src/protocol.ts new file mode 100644 index 0000000..018e43d --- /dev/null +++ b/examples/bun-stream-server/src/protocol.ts @@ -0,0 +1,84 @@ +export type IceServerConfig = { + urls: string[] | string; + username?: string; + credential?: string; +}; + +export type StartStreamRequest = { + url: string; + peerId: string; + iceServers: IceServerConfig[]; +}; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function parseNonEmptyString(value: unknown, fieldName: string): string { + if (typeof value !== "string" || value.trim().length === 0) { + throw new Error(`${fieldName} must be a non-empty string`); + } + + return value.trim(); +} + +export function parseIceServerConfig(value: unknown): IceServerConfig { + if (!isRecord(value)) { + throw new Error("ice server must be an object"); + } + + const rawUrls = value.urls; + const urls = Array.isArray(rawUrls) + ? rawUrls.map((url, index) => parseNonEmptyString(url, `iceServers[].urls[${index}]`)) + : parseNonEmptyString(rawUrls, "iceServers[].urls"); + + if (typeof value.username !== "undefined" && typeof value.username !== "string") { + throw new Error("iceServers[].username must be a string when provided"); + } + + if (typeof value.credential !== "undefined" && typeof value.credential !== "string") { + throw new Error("iceServers[].credential must be a string when provided"); + } + + return { + urls, + username: value.username, + credential: value.credential, + }; +} + +export function parseIceServers(value: unknown): IceServerConfig[] { + if (typeof value === "undefined") { + return []; + } + + if (!Array.isArray(value)) { + throw new Error("iceServers must be an array when provided"); + } + + return value.map(parseIceServerConfig); +} + +export function parseTurnCredentialsResponse(value: unknown): { + iceServers: IceServerConfig[]; +} { + if (!isRecord(value) || !("iceServers" in value)) { + throw new Error("TURN credentials response did not include iceServers"); + } + + return { + iceServers: parseIceServers(value.iceServers), + }; +} + +export function parseStartStreamRequest(value: unknown): StartStreamRequest { + if (!isRecord(value)) { + throw new Error("request body must be an object"); + } + + return { + url: parseNonEmptyString(value.url, "url"), + peerId: parseNonEmptyString(value.peerId, "peerId"), + iceServers: parseIceServers(value.iceServers), + }; +} diff --git a/examples/bun-stream-server/test-end-to-end.js b/examples/bun-stream-server/test-end-to-end.js new file mode 100644 index 0000000..b6045eb --- /dev/null +++ b/examples/bun-stream-server/test-end-to-end.js @@ -0,0 +1,79 @@ +/** + * End-to-End Stream Testing Script + * + * This script helps coordinate testing between the container server + * and the receiver page by providing the receiver ID. + */ + +const crypto = require('crypto'); + +async function testEndToEnd() { + console.log('🧪 Stream Kit End-to-End Test\n'); + + // Generate a consistent receiver ID for this test + const receiverId = 'test-receiver-' + Date.now(); + console.log('📱 Generated Receiver ID:', receiverId); + + // Test 1: Check if container server is running + console.log('\n1️⃣ Checking Container Server...'); + try { + const healthResponse = await fetch('http://localhost:8080/health'); + const health = await healthResponse.json(); + console.log('✅ Container Server:', health.status); + + if (!health.browserActive) { + console.log('⚠️ Browser not active - will be started with stream request'); + } + } catch (error) { + console.log('❌ Container Server not running:', error.message); + console.log(' Run: cd container && bun run src/server.ts'); + return; + } + + // Test 2: Start streaming with our receiver ID + console.log('\n2️⃣ Starting Stream...'); + try { + const streamResponse = await fetch('http://localhost:8080/start-stream', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + url: 'https://www.nytimes.com', // More interesting than example.com + peerId: receiverId + }) + }); + + const result = await streamResponse.json(); + + if (result.status === 'success') { + console.log('✅ Stream started successfully!'); + console.log(' 📡 Source Peer ID:', result.srcPeerId); + console.log(' 🎯 Target Peer ID:', receiverId); + console.log(' 📺 Streaming URL: https://www.nytimes.com'); + + console.log('\n🎬 Ready for Testing!'); + console.log('====================='); + console.log('1. Open receiver.html in Chrome'); + console.log('2. The receiver ID should auto-fill as:', receiverId); + console.log('3. Copy this Source Peer ID:', result.srcPeerId); + console.log('4. Paste it in the receiver page and click Connect'); + console.log('5. You should see the NY Times page streaming!'); + + console.log('\n📊 Connection Monitoring:'); + console.log('• Container polls connections every 15 seconds'); + console.log('• When you connect, activeConnections will show: 1'); + console.log('• When you disconnect, browser shuts down after 60s grace period'); + + console.log('\n🔧 Quick Commands:'); + console.log('• Open receiver: open receiver.html'); + console.log('• Check connections: curl http://localhost:8080/health'); + + } else { + console.log('❌ Stream failed:', result.message); + } + } catch (error) { + console.log('❌ Stream request failed:', error.message); + } +} + +// Run the test +testEndToEnd().catch(console.error); \ No newline at end of file diff --git a/examples/bun-stream-server/test-streaming.js b/examples/bun-stream-server/test-streaming.js new file mode 100644 index 0000000..091dc37 --- /dev/null +++ b/examples/bun-stream-server/test-streaming.js @@ -0,0 +1,85 @@ +/** + * Stream Kit Testing Script + * + * Tests the complete streaming pipeline: + * 1. Container server browser lifecycle + * 2. Stream server routing (dev mode uses localhost:8080) + * 3. Connection tracking and automatic shutdown + */ + +const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +async function test() { + console.log('🧪 Testing Stream Kit Components...\n'); + + // Test 1: Container Server Health + console.log('1️⃣ Testing Container Server Health...'); + try { + const response = await fetch('http://localhost:8080/health'); + const health = await response.json(); + console.log('✅ Container Server:', health.status); + console.log(` Browser Active: ${health.browserActive || false}`); + console.log(` Monitoring Active: ${health.monitoringActive || false}\n`); + } catch (error) { + console.log('❌ Container Server not running:', error.message); + console.log(' Run: cd container && bun run src/server.ts\n'); + return; + } + + // Test 2: Stream Server Health + console.log('2️⃣ Testing Stream Server Health...'); + try { + const response = await fetch('http://localhost:8787/health'); + const health = await response.json(); + console.log('✅ Stream Server:', health.status); + console.log(' Mode: Development (routing to localhost:8080)\n'); + } catch (error) { + console.log('❌ Stream Server not running:', error.message); + console.log(' Run: wrangler dev --env dev\n'); + return; + } + + // Test 3: Browser Lifecycle Test + console.log('3️⃣ Testing Browser Lifecycle Management...'); + console.log(' Starting streaming request...'); + + try { + const streamResponse = await fetch('http://localhost:8080/start-stream', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + url: 'https://www.example.com', + peerId: 'test-receiver-' + Date.now() + }) + }); + + const result = await streamResponse.json(); + + if (result.status === 'success') { + console.log('✅ Streaming started successfully'); + console.log(` Source Peer ID: ${result.srcPeerId}`); + console.log(` Monitoring Active: ${result.monitoringActive}`); + console.log('\n🔍 Connection monitoring will now check every 15 seconds...'); + console.log(' Since there\'s no real receiver, connections will be 0'); + console.log(' Browser should shut down in ~60 seconds of grace period'); + } else { + console.log('❌ Streaming failed:', result.error || result.message); + } + } catch (error) { + console.log('❌ Streaming request failed:', error.message); + } + + console.log('\n📋 Manual Testing Options:'); + console.log(' • Check logs in both terminal windows'); + console.log(' • Browser should start, navigate to example.com'); + console.log(' • After ~75 seconds, browser should auto-shutdown'); + console.log(' • Container server polls window.activeConnections every 15s'); + + console.log('\n🔧 Advanced Testing:'); + console.log(' • Create a real PeerJS receiver to test actual streaming'); + console.log(' • Monitor connection count: window.activeConnections.size'); + console.log(' • Test multiple concurrent streams'); +} + +// Run the tests +test().catch(console.error); \ No newline at end of file diff --git a/examples/bun-stream-server/test/e2e.spec.ts b/examples/bun-stream-server/test/e2e.spec.ts new file mode 100644 index 0000000..cff3bc6 --- /dev/null +++ b/examples/bun-stream-server/test/e2e.spec.ts @@ -0,0 +1,84 @@ +import { expect, test } from "@playwright/test"; + +const previewUrl = process.env.E2E_STREAM_SERVER_URL; + +function buildTraceId() { + return `ci-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; +} + +test.describe("Cloudflare preview stream", () => { + test.skip( + !previewUrl, + "E2E_STREAM_SERVER_URL is required for real-infra streaming tests" + ); + + test("boots the deployed Worker stack and starts a real stream session", async ({ + request, + }, testInfo) => { + const traceId = buildTraceId(); + const sessionId = `ci-session-${Math.random().toString(36).slice(2, 10)}`; + const targetUrl = process.env.E2E_TARGET_URL || "https://example.com"; + + const healthResponse = await request.get(`${previewUrl}/health`); + expect(healthResponse.ok()).toBeTruthy(); + + const healthPayload = await healthResponse.json(); + expect(healthPayload.status).toBe("healthy"); + + const iceResponse = await request.get(`${previewUrl}/ice-servers`, { + headers: { + "x-stream-trace-id": traceId, + "x-stream-session-id": sessionId, + }, + }); + expect(iceResponse.ok()).toBeTruthy(); + + const icePayload = await iceResponse.json(); + expect(Array.isArray(icePayload.iceServers)).toBeTruthy(); + expect(icePayload.iceServers.length).toBeGreaterThan(0); + expect(icePayload.traceId).toBe(traceId); + + const turnUrlSet = new Set( + icePayload.iceServers.flatMap((server: { urls: string | string[] }) => + Array.isArray(server.urls) ? server.urls : [server.urls] + ) + ); + expect([...turnUrlSet].some((url) => url.startsWith("stun:"))).toBeTruthy(); + expect([...turnUrlSet].some((url) => url.startsWith("turn:"))).toBeTruthy(); + + const startResponse = await request.post(`${previewUrl}/start-stream`, { + headers: { + "Content-Type": "application/json", + "x-stream-trace-id": traceId, + "x-stream-session-id": sessionId, + }, + data: { + url: targetUrl, + peerId: `ci-peer-${Math.random().toString(36).slice(2, 10)}`, + iceServers: icePayload.iceServers, + }, + timeout: 120_000, + }); + expect(startResponse.ok()).toBeTruthy(); + + const startPayload = await startResponse.json(); + expect(startPayload.status).toBe("success"); + expect(startPayload.traceId).toBe(traceId); + expect(startPayload.srcPeerId).toBeTruthy(); + expect(startPayload.monitoringActive).toBeTruthy(); + + const diagnostics = { + traceId, + sessionId, + healthPayload, + iceServerCount: icePayload.iceServers.length, + srcPeerId: startPayload.srcPeerId, + monitoringActive: startPayload.monitoringActive, + }; + + await testInfo.attach("preview-smoke", { + body: Buffer.from(JSON.stringify(diagnostics, null, 2), "utf8"), + contentType: "application/json", + }); + }); +}); diff --git a/examples/bun-stream-server/test/index.test.ts b/examples/bun-stream-server/test/index.test.ts new file mode 100644 index 0000000..8ca1b65 --- /dev/null +++ b/examples/bun-stream-server/test/index.test.ts @@ -0,0 +1,182 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import worker from "../src/index"; + +type TestEnv = Parameters[1]; + +function createContainerNamespace(fetchImpl?: (request: Request) => Promise) { + const calls: Request[] = []; + const stubFetch = vi.fn(async (request: Request) => { + calls.push(request); + if (fetchImpl) { + return fetchImpl(request); + } + + return new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }); + + return { + idFromName: vi.fn(() => "stream-id"), + get: vi.fn(() => ({ fetch: stubFetch })), + stubFetch, + calls, + }; +} + +function createEnv(overrides: Partial = {}) { + const container = createContainerNamespace(); + const env: TestEnv = { + CLOUDFLARE_TURN_API_TOKEN: "turn-token", + CLOUDFLARE_TURN_KEY_ID: "turn-key-id", + STREAM_CONTAINER: container as unknown as TestEnv["STREAM_CONTAINER"], + ...overrides, + }; + + return { env, container }; +} + +describe("bun-stream-server worker", () => { + afterEach(() => { + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + }); + + it("returns normalized TURN ice servers", async () => { + const fetchMock = vi.fn(async () => + new Response( + JSON.stringify({ + iceServers: [ + { + urls: [ + "stun:stun.cloudflare.com:3478", + "turn:turn.cloudflare.com:53?transport=udp", + ], + }, + { + urls: "turn:turn.cloudflare.com:5349?transport=tcp", + username: "user", + credential: "pass", + }, + ], + }), + { + status: 200, + headers: { "Content-Type": "application/json" }, + } + ) + ); + vi.stubGlobal("fetch", fetchMock); + + const { env } = createEnv(); + const response = await worker.fetch( + new Request("https://example.com/ice-servers", { + headers: { + "x-stream-trace-id": "trace-ice-1", + }, + }), + env + ); + + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ + iceServers: [ + { + urls: ["stun:stun.cloudflare.com:3478"], + }, + { + urls: ["turn:turn.cloudflare.com:5349?transport=tcp"], + username: "user", + credential: "pass", + }, + ], + sessionId: null, + traceId: "trace-ice-1", + }); + expect(fetchMock).toHaveBeenCalledWith( + "https://rtc.live.cloudflare.com/v1/turn/keys/turn-key-id/credentials/generate-ice-servers", + { + method: "POST", + headers: { + Authorization: "Bearer turn-token", + "Content-Type": "application/json", + }, + body: JSON.stringify({ ttl: 300 }), + } + ); + }); + + it("blocks /debug-state when the debug token is missing", async () => { + const { env, container } = createEnv({ DEBUG_STATE_TOKEN: "top-secret" }); + + const response = await worker.fetch( + new Request("https://example.com/debug-state", { + headers: { + "x-stream-trace-id": "trace-debug-blocked", + }, + }), + env + ); + + expect(response.status).toBe(403); + expect(await response.json()).toEqual({ + error: "Forbidden", + traceId: "trace-debug-blocked", + }); + expect(container.get).not.toHaveBeenCalled(); + expect(container.stubFetch).not.toHaveBeenCalled(); + }); + + it("forwards /debug-state when the debug token matches", async () => { + const { env, container } = createEnv({ DEBUG_STATE_TOKEN: "top-secret" }); + + const response = await worker.fetch( + new Request("https://example.com/debug-state", { + headers: { + "x-stream-trace-id": "trace-debug-allowed", + "x-debug-token": "top-secret", + }, + }), + env + ); + + expect(response.status).toBe(200); + expect(container.get).toHaveBeenCalledOnce(); + expect(container.stubFetch).toHaveBeenCalledOnce(); + + const forwardedRequest = container.calls[0]; + expect(forwardedRequest.headers.get("x-stream-trace-id")).toBe( + "trace-debug-allowed" + ); + }); + + it("routes requests to a session-scoped durable object when a session header is present", async () => { + const { env, container } = createEnv(); + + const response = await worker.fetch( + new Request("https://example.com/start-stream", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-stream-trace-id": "trace-session-1", + "x-stream-session-id": "receiver-session-1", + }, + body: JSON.stringify({ + url: "https://example.com", + peerId: "receiver-123", + iceServers: [], + }), + }), + env + ); + + expect(response.status).toBe(200); + expect(container.idFromName).toHaveBeenCalledWith("session-receiver-session-1"); + + const forwardedRequest = container.calls[0]; + expect(forwardedRequest.headers.get("x-stream-session-id")).toBe( + "receiver-session-1" + ); + }); +}); diff --git a/examples/bun-stream-server/tsconfig.json b/examples/bun-stream-server/tsconfig.json index 6a06624..bc183bd 100644 --- a/examples/bun-stream-server/tsconfig.json +++ b/examples/bun-stream-server/tsconfig.json @@ -3,7 +3,7 @@ "target": "ESNext", "module": "ESNext", "moduleResolution": "node", - "types": ["./worker-configuration.d.ts"], + "types": ["node", "./worker-configuration.d.ts"], "esModuleInterop": true, "strict": true, "skipLibCheck": true, diff --git a/examples/bun-stream-server/worker-configuration.d.ts b/examples/bun-stream-server/worker-configuration.d.ts index d181a4c..529dbac 100644 --- a/examples/bun-stream-server/worker-configuration.d.ts +++ b/examples/bun-stream-server/worker-configuration.d.ts @@ -1,8 +1,11 @@ -// Generated by Wrangler by running `wrangler types` (hash: 95c09f256642a28e3040440af4a953e6) +// Generated by Wrangler by running `wrangler types` (hash: 4fcc58a36afdf2d2cf2f7a4e94908aa1) // Runtime types generated with workerd@1.20250319.0 2025-04-10 nodejs_compat,nodejs_compat_populate_process_env declare namespace Cloudflare { interface Env { - MY_CONTAINER: DurableObjectNamespace; + CLOUDFLARE_TURN_API_TOKEN: string; + CLOUDFLARE_TURN_KEY_ID: string; + DEBUG_STATE_TOKEN?: string; + STREAM_CONTAINER: DurableObjectNamespace; } } interface Env extends Cloudflare.Env {} @@ -1024,7 +1027,7 @@ declare class DigestStream extends WritableStream get bytesWritten(): number | bigint; } /** - * A decoder for a specific method, that is a specific character encoding, like utf-8, iso-8859-2, koi8, cp1261, gbk, etc. A decoder takes a stream of bytes as input and emits a stream of code points. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays. + * A decoder for a specific method, that is a specific character encoding, like utf-8, iso-8859-2, koi8, cp1261, gbk, etc. A decoder takes a stream of bytes as input and emits a stream of code points. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder) */ @@ -1211,7 +1214,7 @@ interface DocumentEnd { append(content: string, options?: ContentOptions): DocumentEnd; } /** - * This is the event type for fetch events dispatched on the service worker global scope. It contains information about the fetch, including the request and how the receiver will treat the response. It provides the event.respondWith() method, which allows us to provide a response to this fetch. + * This is the event type for fetch events dispatched on the service worker global scope. It contains information about the fetch, including the request and how the receiver will treat the response. It provides the event.respondWith() method, which allows us to provide a response to this fetch. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent) */ @@ -1224,7 +1227,7 @@ declare abstract class FetchEvent extends ExtendableEvent { } type HeadersInit = Headers | Iterable> | Record; /** - * This Fetch API interface allows you to perform various actions on HTTP request and response headers. These actions include retrieving, setting, adding to, and removing. A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.  You can add to this using methods like append() (see Examples.) In all methods of this interface, header names are matched by case-insensitive byte sequence. + * This Fetch API interface allows you to perform various actions on HTTP request and response headers. These actions include retrieving, setting, adding to, and removing. A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs. You can add to this using methods like append() (see Examples.) In all methods of this interface, header names are matched by case-insensitive byte sequence. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers) */ diff --git a/examples/bun-stream-server/wrangler.jsonc b/examples/bun-stream-server/wrangler.jsonc index 413407e..a9b1506 100644 --- a/examples/bun-stream-server/wrangler.jsonc +++ b/examples/bun-stream-server/wrangler.jsonc @@ -14,14 +14,14 @@ "containers": [{ "name": "codeflare-containers", "image": "./container/Dockerfile", - "class_name": "MyContainer", - "instances": 2 + "class_name": "StreamContainer", + "max_instances": 2 }], "durable_objects": { "bindings": [ { - "name": "MY_CONTAINER", - "class_name": "MyContainer" + "name": "STREAM_CONTAINER", + "class_name": "StreamContainer" } ] }, @@ -31,10 +31,19 @@ "new_sqlite_classes": [ "MyContainer" ] + }, + { + "tag": "v2", + "new_sqlite_classes": [ + "StreamContainer" + ], + "deleted_classes": [ + "MyContainer" + ] } ], "observability": { "enabled": true, "head_sampling_rate": 1 } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 81ab4d3..dec1dfb 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "clean": "turbo run clean" }, "devDependencies": { - "pnpm": "^9.5.0", - "turbo": "^2.0.9", - "typescript": "^5.4.5" + "pnpm": "^9.15.9", + "turbo": "^2.5.6", + "typescript": "^5.9.2" } -} \ No newline at end of file +} \ No newline at end of file diff --git a/packages/stream-kit-react/README.md b/packages/stream-kit-react/README.md index 8b5268f..b03ccfd 100644 --- a/packages/stream-kit-react/README.md +++ b/packages/stream-kit-react/README.md @@ -1,268 +1,98 @@ # @open-game-system/stream-kit-react -React components for displaying WebRTC streams from the Open Game System (OGS) Cloud Rendering service. +React bindings for an existing `RenderStream`. -## Overview +## What This Package Is -This package provides React components for integrating cloud-rendered streams into your React applications. +This package does not create stream sessions on its own. It wraps a `RenderStream` instance from `@open-game-system/stream-kit-web` and gives you: + +- `StreamProvider` +- `StreamCanvas` +- `createStreamStateContext()` +- `useStream()` + +It is a UI binding layer around an already-created stream object. ## Installation ```bash -npm install @open-game-system/stream-kit-react @open-game-system/stream-kit-web @open-game-system/stream-kit-types -# or pnpm add @open-game-system/stream-kit-react @open-game-system/stream-kit-web @open-game-system/stream-kit-types -# or -yarn add @open-game-system/stream-kit-react @open-game-system/stream-kit-web @open-game-system/stream-kit-types ``` -## Basic Usage - -```tsx -import { StreamProvider, StreamCanvas } from '@open-game-system/stream-kit-react'; -import { createStreamClient } from '@open-game-system/stream-kit-web'; - -// Create the client -const client = createStreamClient({ - brokerUrl: 'https://opengame.tv/stream' -}); +## Current API -function App() { - return ( - -
- {/* Render the video stream */} - -
-
- ); -} -``` +### `StreamProvider` -## Components - -### StreamProvider - -Root provider component that manages the stream client: +`StreamProvider` expects a `stream` prop, not a client: ```tsx - - {/* Your app */} - -``` +import { StreamProvider } from "@open-game-system/stream-kit-react"; -### StreamCanvas - -Component that renders the video stream: - -```tsx - { - console.log('Stream state:', state); - }} -/> + + +; ``` -## Testing +### `StreamCanvas` -The `@open-game-system/stream-kit-testing` package provides a mock client that allows you to simulate stream states and events: +`StreamCanvas` renders the underlying video element from the provided stream: ```tsx -import { render, screen } from '@testing-library/react'; -import { createMockStreamClient } from '@open-game-system/stream-kit-testing'; - -describe('Stream Components', () => { - it('handles stream state changes', async () => { - const mockClient = createMockStreamClient(); - const onStateChange = vi.fn(); - - render( - - - - ); - - // Initial state should be connecting - expect(onStateChange).toHaveBeenCalledWith(expect.objectContaining({ - status: 'connecting' - })); - - // Simulate successful connection - await mockClient.simulateStreamState({ - status: 'streaming', - fps: 60, - latency: 50 - }); - - // Should update with new state - expect(onStateChange).toHaveBeenCalledWith(expect.objectContaining({ - status: 'streaming', - fps: 60, - latency: 50 - })); - - // Simulate error - await mockClient.simulateStreamState({ - status: 'error', - error: new Error('Connection lost') - }); +import { StreamCanvas } from "@open-game-system/stream-kit-react"; - expect(onStateChange).toHaveBeenCalledWith(expect.objectContaining({ - status: 'error', - error: expect.any(Error) - })); - }); - - it('handles stream events', async () => { - const mockClient = createMockStreamClient(); - - const { container } = render( - - { - if (event.type === 'click') { - container.querySelector('.overlay')?.classList.add('clicked'); - } - }} - /> - - ); - - // Simulate stream starting - await mockClient.simulateStreamState({ status: 'streaming' }); - - // Simulate receiving a click event from the stream - await mockClient.simulateStreamEvent({ - type: 'click', - position: { x: 100, y: 100 } - }); - - expect(container.querySelector('.overlay.clicked')).toBeInTheDocument(); - }); - - it('handles stream quality changes', async () => { - const mockClient = createMockStreamClient(); - const onQualityChange = vi.fn(); - - render( - - - - ); - - // Simulate quality degradation - await mockClient.simulateStreamQuality({ - resolution: '720p', - quality: 'medium', - reason: 'bandwidth' - }); - - expect(onQualityChange).toHaveBeenCalledWith({ - resolution: '720p', - quality: 'medium', - reason: 'bandwidth' - }); - }); -}); +function Player() { + return ; +} ``` -The mock client provides several methods for testing: +### Full example -- `simulateStreamState(state)`: Change the stream's state (connecting, streaming, error, etc.) -- `simulateStreamEvent(event)`: Simulate receiving an event from the stream -- `simulateStreamQuality(quality)`: Simulate stream quality changes -- `simulateDisconnect()`: Simulate unexpected disconnection -- `simulateReconnect()`: Simulate successful reconnection - -This allows you to test all aspects of your stream integration, including: -- State transitions -- Event handling -- Quality adaptations -- Error scenarios -- Reconnection logic +```tsx +import { createStreamClient } from "@open-game-system/stream-kit-web"; +import { StreamCanvas, StreamProvider } from "@open-game-system/stream-kit-react"; -## Examples +const client = createStreamClient({ + brokerUrl: "https://api.example.com", +}); -### Multiple Views +const stream = client.createRenderStream({ + url: "https://your-game.com/render/world", +}); -```tsx -function GameWithMultipleViews() { +function App() { return ( - -
- - -
+ + ); } ``` -### With Loading States +### Stream state helpers -```tsx -function StreamWithStates() { - return ( - -
- { - if (state.status === 'connecting') { - // Show loading UI - } else if (state.status === 'streaming') { - // Show connected UI - } else if (state.status === 'error') { - // Show error UI - } - }} - /> -
-
- ); -} -``` +`createStreamStateContext()` returns selector helpers and small state components such as: + +- `Status` +- `When` +- `Stats` +- `Quality` +- `Match` +- `Overlay` + +These are useful when you want to subscribe to only part of the stream state. -## Best Practices +## Notes -1. Use a single `StreamProvider` at the root of your app -2. Each `StreamCanvas` manages its own stream instance -3. Handle stream states via the `onStateChange` prop -4. Use the testing utilities for reliable tests +- This package is intentionally narrow: it binds React to a `RenderStream`. +- It is not yet the future OGS product SDK shape described in `docs/integration.md`. +- The earlier README examples that passed `client` to `StreamProvider` were incorrect for the current code. -## TypeScript Support +## Related Packages -All components are fully typed. The stream state types are inherited from `@open-game-system/stream-kit-types`. +- [`@open-game-system/stream-kit-web`](../stream-kit-web/README.md) +- [`@open-game-system/stream-kit-types`](../stream-kit-types/README.md) +- [`@open-game-system/stream-kit-testing`](../stream-kit-testing/README.md) ## License -MIT License \ No newline at end of file +MIT diff --git a/packages/stream-kit-react/package.json b/packages/stream-kit-react/package.json index 4922c79..9560964 100644 --- a/packages/stream-kit-react/package.json +++ b/packages/stream-kit-react/package.json @@ -29,16 +29,16 @@ "typescript": ">=4.5.0" }, "devDependencies": { - "rimraf": "^5.0.0", - "tsup": "^8.0.0", - "typescript": "^5.4.5", - "react": "^18.2.0", - "@types/react": "^18.2.0", - "vitest": "^1.6.0", - "@vitest/coverage-v8": "^1.6.0", - "jsdom": "^24.0.0", - "@testing-library/react": "^16.0.0", - "@testing-library/jest-dom": "^6.0.0" + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^16.3.0", + "@types/react": "^18.3.24", + "@vitest/coverage-v8": "^1.6.1", + "jsdom": "^24.1.3", + "react": "^18.3.1", + "rimraf": "^5.0.10", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "vitest": "^1.6.1" }, "publishConfig": { "access": "public" @@ -55,4 +55,4 @@ ], "author": "OpenGameSystem", "license": "MIT" -} \ No newline at end of file +} \ No newline at end of file diff --git a/packages/stream-kit-server/PLAN.md b/packages/stream-kit-server/PLAN.md new file mode 100644 index 0000000..d1cb850 --- /dev/null +++ b/packages/stream-kit-server/PLAN.md @@ -0,0 +1,205 @@ +# Plan: Refactor State Subscription to WebSockets for Cloudflare Workers + +**Goal:** Modify the `@open-game-system/stream-kit-server` package to use WebSockets for real-time state change notifications to clients, replacing the current Server-Sent Events (SSE) implementation. This implementation will be tailored for the Cloudflare Workers runtime, using KV for state persistence and Workers/Durable Objects for WebSocket management. + +**Key Decisions:** + +1. **WebSocket Only:** SSE will be completely removed. +2. **Endpoint:** WebSocket upgrades will be handled on `GET /stream/:streamId` requests containing an `Upgrade: websocket` header. +3. **No Generic Hooks Abstraction (Initially):** The `StreamKitHooks` abstraction will be removed. State persistence will directly use Cloudflare KV. WebSocket connection management will be handled within the Worker, with a recommendation for Durable Objects for scalability. +4. **Cloudflare-Specific Implementation:** The router will be designed to work with Cloudflare Worker environment bindings (`env` object typically containing KV namespaces, and enabling WebSocket upgrades). + +## Changes Required: + +1. **Update `src/types.ts` (or create new type definitions): + * Remove the `StreamKitHooks` interface. + * Define the structure for WebSocket messages if needed (e.g., `StateChange` for snapshots/patches, though sending full snapshots might be simpler initially). + * Define the expected structure of the environment object `TEnv` passed to the router, specifically that it should contain a KV namespace binding (e.g., `STATE_KV: KVNamespace`). + +2. **Refactor Router Logic (`src/router.ts`): + * **Remove SSE Logic:** All code related to `/sse` endpoints and `AsyncIterable` will be removed. + * **Cloudflare KV Integration:** + * Replace `hooks.loadStreamState` with `env.STATE_KV.get(streamId, "json")`. + * Replace `hooks.saveStreamState` with `env.STATE_KV.put(streamId, JSON.stringify(state))`. + * Replace `hooks.deleteStreamState` with `env.STATE_KV.delete(streamId)`. + * **WebSocket Connection Management (In-Worker for V1, DO recommended for V2):** + * An in-memory `Map>` (e.g., `streamSubscribers`) will be used to store active WebSocket connections per `streamId`. This is suitable for a single Worker instance or for routing to a Durable Object. + * **Upgrade Handling:** When a `GET /stream/:streamId` request with an `Upgrade: websocket` header is received: + 1. Use Cloudflare's `WebSocketPair`: `const { 0: client, 1: server } = new WebSocketPair();`. + 2. Call `server.accept()`. + 3. Send the current full state (snapshot from KV) to the `server` WebSocket. + 4. Store the `server` WebSocket in `streamSubscribers` for the given `streamId`. + 5. Handle `server.onmessage`, `server.onclose`, `server.onerror`. + 6. Return `new Response(null, { status: 101, webSocket: client });`. + * **Broadcasting State Changes:** + * After a successful `env.STATE_KV.put()` (from a `POST /stream/:streamId` request), iterate over `streamSubscribers.get(streamId)` and send the updated state (full snapshot) to each connected WebSocket. + +3. **Update Mocking and Tests (`src/mock-hooks.ts` will be removed, `src/router.test.ts` refactored): + * Remove `src/mock-hooks.ts`. + * `router.test.ts` will need to: + * Mock the Cloudflare KV namespace (`env.STATE_KV`). + * Mock the `WebSocketPair` and WebSocket server/client behavior for testing connection upgrades and message exchanges. Vitest or Jest provide ways to mock global objects or classes. + * Verify direct KV interactions. + * Test WebSocket upgrade, initial snapshot sending, and broadcast on state change. + +4. **API Documentation (`README.md`): + * Reflect the removal of SSE and the hook-based architecture. + * Specify that the server is designed for Cloudflare Workers. + * Detail the WebSocket endpoint (`GET /stream/:streamId` with `Upgrade` header). + * Document the direct dependency on a KV namespace in the Worker environment. + * Recommend Durable Objects for scalable WebSocket management. + +## Detailed Implementation Steps & Code Examples (Illustrative for Cloudflare Workers): + +### Step 1: Modify `src/types.ts` + +```typescript +// src/types.ts + +// Example: Define the type for messages sent over WebSocket +export interface WebSocketMessage { + type: 'snapshot'; // Initially, only send full snapshots + data: any; // The stream state +} + +// Define the expected Cloudflare environment structure for the router +export interface StreamKitEnv { + STATE_KV: KVNamespace; // KV namespace for storing stream states + // Potentially a Durable Object binding for WebSocket handling in a scalable setup + // STREAM_WEBSOCKET_DO?: DurableObjectNamespace; +} +``` + +### Step 2: Refactor `src/router.ts` + +```typescript +// src/router.ts +import type { StreamKitEnv, WebSocketMessage } from "./types"; + +// This Map is instance-local. For multi-instance, use Durable Objects. +const streamSubscribers = new Map>(); + +export function createStreamKitRouter() { + return async (request: Request, env: TEnv): Promise => { + const url = new URL(request.url); + const pathSegments = url.pathname.split("/").filter(Boolean); + + if (pathSegments.length < 2 || pathSegments[0] !== "stream") { + return new Response("Not Found", { status: 404 }); + } + const streamId = pathSegments[1]; + + // Handle WebSocket Upgrade for GET /stream/:streamId + if (request.method === "GET" && request.headers.get("Upgrade")?.toLowerCase() === "websocket") { + if (pathSegments.length !== 2) { // Ensure it's exactly /stream/:streamId + return new Response("Invalid WebSocket path", { status: 400 }); + } + + const pair = new WebSocketPair(); + const clientWs = pair[0]; + const serverWs = pair[1]; + + serverWs.accept(); + + const subscribers = streamSubscribers.get(streamId) || new Set(); + subscribers.add(serverWs); + streamSubscribers.set(streamId, subscribers); + + // Send initial state snapshot + try { + const initialState = await env.STATE_KV.get(streamId, "json"); + if (initialState) { + const message: WebSocketMessage = { type: 'snapshot', data: initialState }; + serverWs.send(JSON.stringify(message)); + } else { + // Optionally send an error or close if state must exist + // serverWs.send(JSON.stringify({ type: 'error', data: 'Stream state not found' })); + } + } catch (e) { + console.error(`Failed to send initial state for ${streamId}:`, e); + // serverWs.close(1011, "Error fetching initial state"); // Optional: close with error + } + + serverWs.addEventListener("message", event => { + // Handle incoming messages from client (e.g., keep-alive, client-side commands) + // console.log(`Received message from ${streamId}:`, event.data); + }); + + serverWs.addEventListener("close", () => { + subscribers.delete(serverWs); + if (subscribers.size === 0) { + streamSubscribers.delete(streamId); + } + console.log(`WebSocket closed for stream ${streamId}`); + }); + + serverWs.addEventListener("error", err => { + subscribers.delete(serverWs); + if (subscribers.size === 0) { + streamSubscribers.delete(streamId); + } + console.error(`WebSocket error for stream ${streamId}:`, err); + }); + + return new Response(null, { status: 101, webSocket: clientWs }); + } + + // RESTful API for state management + try { + switch (request.method) { + case "GET": // Get current state (non-WebSocket) + const state = await env.STATE_KV.get(streamId, "json"); + if (state === null) return new Response("Stream Not Found", { status: 404 }); + return new Response(JSON.stringify(state), { headers: { "Content-Type": "application/json" } }); + + case "POST": + const body = await request.json(); + await env.STATE_KV.put(streamId, JSON.stringify(body)); + + // Broadcast the new state to WebSocket subscribers + const subscribers = streamSubscribers.get(streamId); + if (subscribers) { + const message: WebSocketMessage = { type: 'snapshot', data: body }; + const serializedMessage = JSON.stringify(message); + subscribers.forEach(ws => { + if (ws.readyState === WebSocket.OPEN) { // Check if WebSocket is open + ws.send(serializedMessage); + } + }); + } + return new Response("Stream state saved", { status: 200 }); + + case "DELETE": + await env.STATE_KV.delete(streamId); + // Optionally notify subscribers of deletion, or just close connections + const activeSubscribers = streamSubscribers.get(streamId); + if (activeSubscribers) { + activeSubscribers.forEach(ws => ws.close(1000, "Stream deleted")); + streamSubscribers.delete(streamId); + } + return new Response("Stream deleted", { status: 200 }); + + default: + return new Response("Method Not Allowed", { status: 405 }); + } + } catch (error) { + console.error(`API Error for stream ${streamId}:`, error); + return new Response("Internal Server Error", { status: 500 }); + } + }; +} +``` + +## Considerations for Scalability (Durable Objects): + +While the above in-Worker `streamSubscribers` Map works for a single instance, it does not scale across multiple Cloudflare Worker instances. For a production system, each `streamId` should ideally be managed by a **Durable Object (DO)**. + +* **DO Responsibilities:** + * Store the state for its `streamId` (internally, could still use KV or its own storage API). + * Manage all WebSocket connections for its `streamId`. + * Handle broadcasts to its connected WebSockets when its state changes. +* **Main Worker Router:** + * HTTP GET/POST/DELETE requests for `/stream/:streamId` would be routed to the `fetch` handler of the corresponding DO instance (e.g., `env.STREAM_WEBSOCKET_DO.get(DO_ID).fetch(request)`). + * WebSocket upgrade requests for `/stream/:streamId` would also be forwarded to the DO, which would then accept and manage the WebSocket session. + +This DO-based architecture is the standard way to handle stateful WebSockets at scale on Cloudflare Workers. The initial implementation can start with the in-Worker map for simplicity, with a clear path to refactor towards Durable Objects for production readiness. \ No newline at end of file diff --git a/packages/stream-kit-server/README.md b/packages/stream-kit-server/README.md index 4ee064e..a96d64e 100644 --- a/packages/stream-kit-server/README.md +++ b/packages/stream-kit-server/README.md @@ -1,144 +1,68 @@ # @open-game-system/stream-kit-server -Server-side implementation for the Open Game System (OGS). +Server-side primitives for the Stream Kit package layer. -## Overview +## What This Package Is -This package provides a hook-based router/middleware (`createStreamKitRouter`) for managing stream state persistence. By implementing `StreamKitHooks`, you can integrate with various storage backends (Redis, Cloudflare KV, etc.) to save, load, and delete stream state. +Today this package contains two things: -This package is designed to be used alongside other OGS components or custom logic that handles the actual browser automation (like Puppeteer) and WebRTC streaming. +- `createStreamKitRouter()` for storage-backed stream state endpoints +- newer `StreamKitServer` exports that are still incomplete relative to the deployed example + +The stable, tested piece here is the router. ## Installation ```bash -npm install @open-game-system/stream-kit-server @open-game-system/stream-kit-types -# or pnpm add @open-game-system/stream-kit-server @open-game-system/stream-kit-types -# or -yarn add @open-game-system/stream-kit-server @open-game-system/stream-kit-types -``` - -**Requirements:** - -- For Puppeteer-based rendering, your server environment needs Google Chrome/Chromium and ffmpeg installed. -- A Dockerfile setup will be required for production deployments. Documentation for this will be provided separately. -- For the hook-based router, install the necessary client library for your chosen storage (e.g., `ioredis`). - -## Usage (Hook-Based Architecture) - -The core idea is to implement storage interactions via hooks and pass them to the `createStreamKitRouter` factory. - -### Example: Using Redis for Storage (Node.js/Bun) - -1. **Define Environment/Context:** - -```typescript -// src/redis-client.ts -import Redis from "ioredis"; - -const redisClient = new Redis(process.env.REDIS_URL || "redis://localhost:6379"); - -export default redisClient; - -export interface AppEnv { - redis: Redis.Redis; -} ``` -2. **Implement Storage Hooks:** - -```typescript -// src/streamkit-hooks-redis.ts -import type { StreamKitHooks } from "@open-game-collective/stream-kit-server"; -import type { AppEnv } from "./redis-client"; -import redisClient from "./redis-client"; - -const getStreamKey = (streamId: string) => `stream:${streamId}:state`; - -export const redisStreamKitHooks: StreamKitHooks = { - async saveStreamState({ streamId, state, env }) { - const redis = redisClient; - await redis.set(getStreamKey(streamId), JSON.stringify(state)); - }, - - async loadStreamState({ streamId, env }) { - const redis = redisClient; - const stateJson = await redis.get(getStreamKey(streamId)); - return stateJson ? JSON.parse(stateJson) : null; - }, - - async deleteStreamState({ streamId, env }) { - const redis = redisClient; - await redis.del(getStreamKey(streamId)); - }, - - async *subscribeToStateChanges({ streamId, env, lastEventId }) { - // Implement your state change subscription logic here - // This could use Redis pub/sub, WebSocket connections, etc. - // Must yield StateChange objects: { type: 'patch' | 'snapshot', data: unknown, id?: string } - } -}; -``` - -3. **Integrate Router:** - -```typescript -// src/server.ts -import { createStreamKitRouter } from "@open-game-collective/stream-kit-server"; -import { redisStreamKitHooks } from "./streamkit-hooks-redis"; -import redisClient from "./redis-client"; - -// Create router with hooks -const streamRouterHandler = createStreamKitRouter({ - hooks: redisStreamKitHooks -}); - -// Create environment object that includes storage -const env: AppEnv = { - redis: redisClient -}; - -// Set up server (example using Bun) -Bun.serve({ - port: 3000, - async fetch(request: Request) { - const url = new URL(request.url); - - if (url.pathname.startsWith("/stream/")) { - return streamRouterHandler(request, env); - } - - return new Response("Not Found", { status: 404 }); +## `createStreamKitRouter()` + +This helper builds a request handler around storage hooks you provide. + +Supported routes: + +- `GET /stream/:streamId` +- `GET /stream/:streamId/sse` +- `POST /stream/:streamId` +- `DELETE /stream/:streamId` + +### Example + +```ts +import { createStreamKitRouter } from "@open-game-system/stream-kit-server"; + +const router = createStreamKitRouter({ + hooks: { + async saveStreamState({ streamId, state, env }) { + await env.kv.put(streamId, JSON.stringify(state)); + }, + async loadStreamState({ streamId, env }) { + const value = await env.kv.get(streamId); + return value ? JSON.parse(value) : null; + }, + async deleteStreamState({ streamId, env }) { + await env.kv.delete(streamId); + }, + async *subscribeToStateChanges() { + // Yield snapshot/patch events here + }, }, }); ``` -## API Reference - -### `createStreamKitRouter(config)` - -Creates a request handler function suitable for environments like Cloudflare Workers, Node.js, Deno, or Bun. - -- `config`: Configuration object - - `hooks: StreamKitHooks`: Required. An object containing your implementations of the storage interaction hooks. -- Returns: `(request: Request, env: TEnv) => Promise` - -**Endpoints:** - -- `GET /stream/:streamId`: Loads stream state -- `GET /stream/:streamId/sse`: Server-Sent Events for state changes -- `POST /stream/:streamId`: Saves stream state (expects JSON body) -- `DELETE /stream/:streamId`: Deletes stream state +## Notes -### `StreamKitHooks` Interface +- This package is not what powers the working Cloudflare example under `examples/bun-stream-server/`. +- The exported `StreamKitServer` and `createStreamClient` server-side API are not yet the source of truth for deployed behavior. +- If you want the working end-to-end runtime, look at the example and the repo-level docs instead. -This interface defines the required functions for handling storage operations: +## Related Packages -- `saveStreamState(params: { streamId: string; state: unknown; env: TEnv }): Promise` -- `loadStreamState(params: { streamId: string; env: TEnv }): Promise` -- `deleteStreamState(params: { streamId: string; env: TEnv }): Promise` -- `subscribeToStateChanges(params: { streamId: string; env: TEnv; lastEventId?: string }): AsyncIterable` +- [`@open-game-system/stream-kit-types`](../stream-kit-types/README.md) +- [`@open-game-system/stream-kit-web`](../stream-kit-web/README.md) ## License -MIT License \ No newline at end of file +MIT diff --git a/packages/stream-kit-server/package.json b/packages/stream-kit-server/package.json index 783ecd6..3f3087c 100644 --- a/packages/stream-kit-server/package.json +++ b/packages/stream-kit-server/package.json @@ -25,19 +25,19 @@ "@open-game-system/stream-kit-types": "workspace:*", "fast-json-patch": "^3.1.1", "install": "^0.13.0", - "peerjs": "^1.5.4", - "puppeteer": "22.13.1", + "peerjs": "^1.5.5", + "puppeteer": "24.20.0", "puppeteer-stream": "3.0.20" }, "devDependencies": { - "@types/node": "^18.11.18", + "@types/node": "^18.19.124", "@types/peerjs": "^1.1.0", - "@types/ws": "^8.5.11", - "@vitest/coverage-v8": "^1.6.0", - "rimraf": "^5.0.5", - "tsup": "^8.0.2", - "typescript": "^5.3.3", - "vitest": "^1.6.0" + "@types/ws": "^8.18.1", + "@vitest/coverage-v8": "^1.6.1", + "rimraf": "^5.0.10", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "vitest": "^1.6.1" }, "publishConfig": { "access": "public" diff --git a/packages/stream-kit-server/src/client.ts b/packages/stream-kit-server/src/client.ts new file mode 100644 index 0000000..ccbaa93 --- /dev/null +++ b/packages/stream-kit-server/src/client.ts @@ -0,0 +1,117 @@ +import { RenderStreamConfig } from './types'; + +export interface StreamClientConfig { + host: string; + port?: number; +} + +export interface RenderStream { + id: string; + status: 'starting' | 'running' | 'stopping' | 'stopped' | 'error'; + connect(peerId: string): Promise; + disconnect(): Promise; + getStatus(): Promise<{ status: string; peerId?: string }>; +} + +export class StreamClient { + private baseUrl: string; + + constructor(private config: StreamClientConfig) { + const port = config.port ? `:${config.port}` : ''; + this.baseUrl = `https://${config.host}${port}`; + } + + async createRenderStream(config: RenderStreamConfig): Promise { + const response = await fetch(`${this.baseUrl}/stream`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(config), + }); + + if (!response.ok) { + throw new Error(`Failed to create stream: ${response.statusText}`); + } + + const { sessionId } = await response.json(); + return new RenderStreamImpl(this.baseUrl, sessionId); + } + + async getStream(sessionId: string): Promise { + try { + const response = await fetch(`${this.baseUrl}/stream/${sessionId}`); + + if (response.status === 404) { + return null; + } + + if (!response.ok) { + throw new Error(`Failed to get stream: ${response.statusText}`); + } + + const session = await response.json(); + return new RenderStreamImpl(this.baseUrl, sessionId, session.status); + } catch (error) { + console.error('Failed to get stream:', error); + return null; + } + } +} + +class RenderStreamImpl implements RenderStream { + constructor( + private baseUrl: string, + public id: string, + public status: RenderStream['status'] = 'starting' + ) {} + + async connect(peerId: string): Promise { + const response = await fetch(`${this.baseUrl}/stream/${this.id}/connect`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ peerId }), + }); + + if (!response.ok) { + throw new Error(`Failed to connect to stream: ${response.statusText}`); + } + + const result = await response.json(); + this.status = result.status; + } + + async disconnect(): Promise { + const response = await fetch(`${this.baseUrl}/stream/${this.id}`, { + method: 'DELETE', + }); + + if (!response.ok) { + throw new Error(`Failed to disconnect from stream: ${response.statusText}`); + } + + this.status = 'stopped'; + } + + async getStatus(): Promise<{ status: string; peerId?: string }> { + const response = await fetch(`${this.baseUrl}/stream/${this.id}`); + + if (!response.ok) { + throw new Error(`Failed to get stream status: ${response.statusText}`); + } + + const session = await response.json(); + this.status = session.status; + + return { + status: session.status, + peerId: session.peerId, + }; + } +} + +export function createStreamClient(config: StreamClientConfig): StreamClient { + return new StreamClient(config); +} \ No newline at end of file diff --git a/packages/stream-kit-server/src/container-manager.ts b/packages/stream-kit-server/src/container-manager.ts new file mode 100644 index 0000000..ea8883a --- /dev/null +++ b/packages/stream-kit-server/src/container-manager.ts @@ -0,0 +1,139 @@ +import { ContainerManager, RenderStreamConfig } from './types'; +import { spawn } from 'child_process'; +import { promisify } from 'util'; + +export interface DockerContainerConfig { + image: string; + port: number; + extensionPath?: string; +} + +export class DockerContainerManager implements ContainerManager { + private containers = new Map(); + private nextPort = 8080; + + constructor(private config: DockerContainerConfig) {} + + async start(sessionId: string, renderConfig: RenderStreamConfig): Promise<{ containerId: string; port: number }> { + const port = this.nextPort++; + const containerId = `stream-kit-${sessionId}`; + + // Build docker run command + const dockerArgs = [ + 'run', + '-d', // detached + '--name', containerId, + '-p', `${port}:${this.config.port}`, + '--rm', // auto-remove when stopped + ]; + + // Mount extension if provided + if (this.config.extensionPath) { + dockerArgs.push('-v', `${this.config.extensionPath}:/app/extension:ro`); + } + + // Add environment variables + dockerArgs.push( + '-e', `RENDER_URL=${renderConfig.url}`, + '-e', `RENDER_WIDTH=${renderConfig.width || 1920}`, + '-e', `RENDER_HEIGHT=${renderConfig.height || 1080}`, + '-e', `DEVICE_SCALE_FACTOR=${renderConfig.deviceScaleFactor || 1}`, + ); + + dockerArgs.push(this.config.image); + + try { + // Execute docker run + await this.execCommand('docker', dockerArgs); + this.containers.set(containerId, { containerId, port, sessionId }); + return { containerId, port }; + } catch (error) { + throw new Error(`Failed to start container: ${error}`); + } + } + + async stop(containerId: string): Promise { + try { + await this.execCommand('docker', ['stop', containerId]); + this.containers.delete(containerId); + } catch (error) { + console.error(`Failed to stop container ${containerId}:`, error); + // Try force removal + try { + await this.execCommand('docker', ['rm', '-f', containerId]); + this.containers.delete(containerId); + } catch (forceError) { + console.error(`Failed to force remove container ${containerId}:`, forceError); + } + } + } + + async getStatus(containerId: string): Promise<'starting' | 'running' | 'stopping' | 'stopped' | 'error'> { + try { + const stdout = await this.execCommand('docker', ['inspect', '--format', '{{.State.Status}}', containerId]); + const status = stdout.trim(); + + switch (status) { + case 'created': + case 'restarting': + return 'starting'; + case 'running': + return 'running'; + case 'paused': + return 'stopping'; + case 'exited': + case 'dead': + return 'stopped'; + default: + return 'error'; + } + } catch (error) { + return 'error'; + } + } + + async cleanup(): Promise { + const containerIds = Array.from(this.containers.keys()); + + await Promise.all( + containerIds.map(async (containerId) => { + try { + await this.stop(containerId); + } catch (error) { + console.error(`Failed to cleanup container ${containerId}:`, error); + } + }) + ); + + this.containers.clear(); + } + + private async execCommand(command: string, args: string[]): Promise { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] }); + + let stdout = ''; + let stderr = ''; + + child.stdout?.on('data', (data) => { + stdout += data.toString(); + }); + + child.stderr?.on('data', (data) => { + stderr += data.toString(); + }); + + child.on('error', (error) => { + reject(error); + }); + + child.on('close', (code) => { + if (code === 0) { + resolve(stdout); + } else { + reject(new Error(`Command failed with exit code ${code}: ${stderr}`)); + } + }); + }); + } +} \ No newline at end of file diff --git a/packages/stream-kit-server/src/index.ts b/packages/stream-kit-server/src/index.ts index 2631123..bc06ed0 100644 --- a/packages/stream-kit-server/src/index.ts +++ b/packages/stream-kit-server/src/index.ts @@ -1,3 +1,12 @@ -// Re-export the hook-based router components +// Export the new client-based API +export { StreamKitServer } from "./server"; +export { createStreamClient } from "./client"; +export type { + StreamKitServerConfig, + StreamSession, + RenderStreamConfig +} from "./types"; + +// Legacy exports (deprecated) export { createStreamKitRouter } from "./router"; export type { StreamKitHooks, StateChange } from "./types"; \ No newline at end of file diff --git a/packages/stream-kit-server/src/server.ts b/packages/stream-kit-server/src/server.ts new file mode 100644 index 0000000..e920989 --- /dev/null +++ b/packages/stream-kit-server/src/server.ts @@ -0,0 +1,123 @@ +import { StreamKitServerConfig, StreamSession, RenderStreamConfig, ContainerManager } from './types'; +import { DockerContainerManager } from './container-manager'; + +export class StreamKitServer { + private sessions = new Map(); + private containerManager: ContainerManager; + + constructor(private config: StreamKitServerConfig) { + this.containerManager = new DockerContainerManager({ + image: config.containerImage || 'stream-kit-container', + port: config.containerPort || 8080, + extensionPath: config.extensionPath, + }); + } + + async createStream(config: RenderStreamConfig): Promise<{ sessionId: string }> { + const sessionId = generateSessionId(); + + const session: StreamSession = { + id: sessionId, + url: config.url, + status: 'starting', + createdAt: new Date(), + lastActiveAt: new Date(), + config, + }; + + this.sessions.set(sessionId, session); + + // Start container in background + this.startContainer(sessionId).catch((error) => { + console.error(`Failed to start container for session ${sessionId}:`, error); + this.updateSessionStatus(sessionId, 'error'); + }); + + return { sessionId }; + } + + async connectToStream(sessionId: string, peerId: string): Promise<{ status: string; peerId?: string }> { + const session = this.sessions.get(sessionId); + if (!session) { + throw new Error(`Session ${sessionId} not found`); + } + + session.peerId = peerId; + session.lastActiveAt = new Date(); + this.sessions.set(sessionId, session); + + // TODO: Notify container about peer connection + + return { + status: session.status, + peerId: session.peerId, + }; + } + + async getStream(sessionId: string): Promise { + const session = this.sessions.get(sessionId); + if (session) { + session.lastActiveAt = new Date(); + this.sessions.set(sessionId, session); + } + return session || null; + } + + async deleteStream(sessionId: string): Promise { + const session = this.sessions.get(sessionId); + if (!session) { + return; + } + + this.updateSessionStatus(sessionId, 'stopping'); + + if (session.containerId) { + try { + await this.containerManager.stop(session.containerId); + } catch (error) { + console.error(`Failed to stop container ${session.containerId}:`, error); + } + } + + this.sessions.delete(sessionId); + } + + async getActiveSessions(): Promise { + return Array.from(this.sessions.values()); + } + + async cleanup(): Promise { + await this.containerManager.cleanup(); + } + + private async startContainer(sessionId: string): Promise { + const session = this.sessions.get(sessionId); + if (!session) { + throw new Error(`Session ${sessionId} not found`); + } + + try { + const { containerId } = await this.containerManager.start(sessionId, session.config); + + session.containerId = containerId; + session.status = 'running'; + this.sessions.set(sessionId, session); + } catch (error) { + this.updateSessionStatus(sessionId, 'error'); + throw error; + } + } + + private updateSessionStatus(sessionId: string, status: StreamSession['status']): void { + const session = this.sessions.get(sessionId); + if (session) { + session.status = status; + session.lastActiveAt = new Date(); + this.sessions.set(sessionId, session); + } + } +} + +function generateSessionId(): string { + return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); +} \ No newline at end of file diff --git a/packages/stream-kit-server/src/types.ts b/packages/stream-kit-server/src/types.ts index d03937d..ba55f6a 100644 --- a/packages/stream-kit-server/src/types.ts +++ b/packages/stream-kit-server/src/types.ts @@ -61,6 +61,43 @@ export interface StreamKitHooks { }) => AsyncIterable; } +// New client-based API types +export interface StreamKitServerConfig { + host: string; + port?: number; + containerImage?: string; + containerPort?: number; + extensionPath?: string; + maxStreams?: number; + streamTimeout?: number; +} + +export interface RenderStreamConfig { + url: string; + width?: number; + height?: number; + deviceScaleFactor?: number; + timeout?: number; +} + +export interface StreamSession { + id: string; + url: string; + status: 'starting' | 'running' | 'stopping' | 'stopped' | 'error'; + createdAt: Date; + lastActiveAt: Date; + config: RenderStreamConfig; + containerId?: string; + peerId?: string; +} + +export interface ContainerManager { + start(sessionId: string, config: RenderStreamConfig): Promise<{ containerId: string; port: number }>; + stop(containerId: string): Promise; + getStatus(containerId: string): Promise<'starting' | 'running' | 'stopping' | 'stopped' | 'error'>; + cleanup(): Promise; +} + // TODO: Import or define StreamState from @open-game-system/stream-kit-types // export type StreamState = { /* ... structure of your stream state ... */ }; // export type StreamMetadata = { /* ... structure of your stream metadata ... */ }; \ No newline at end of file diff --git a/packages/stream-kit-testing/README.md b/packages/stream-kit-testing/README.md index 3f4478c..8c7118b 100644 --- a/packages/stream-kit-testing/README.md +++ b/packages/stream-kit-testing/README.md @@ -1,204 +1,80 @@ # @open-game-system/stream-kit-testing -Testing utilities for Stream Kit packages that make it easy to test stream-based applications and components. +Testing helpers for the current Stream Kit package layer. -## Installation +## What This Package Is -```bash -npm install --save-dev @open-game-system/stream-kit-testing -# or -pnpm add -D @open-game-system/stream-kit-testing -# or -yarn add -D @open-game-system/stream-kit-testing -``` +This package provides: -## Usage +- `createMockStreamClient()` +- broker event simulation helpers +- stream assertions -### Mock Stream Client +It is meant for testing code built against `@open-game-system/stream-kit-web` and `@open-game-system/stream-kit-react`. -The mock stream client allows you to simulate stream behavior in your tests without actual WebRTC connections. +## Installation -```typescript -import { createMockStreamClient } from '@open-game-system/stream-kit-testing'; -import { expect, test } from 'vitest'; +```bash +pnpm add -D @open-game-system/stream-kit-testing +``` -test('stream client handles connection lifecycle', async () => { - const mockClient = createMockStreamClient(); - - const stream = mockClient.createRenderStream({ - url: 'https://test.com/stream', - renderOptions: { resolution: '1080p' } - }); +## Mock client - // Test initial state - expect(stream.state.status).toBe('initialized'); +```ts +import { createMockStreamClient } from "@open-game-system/stream-kit-testing"; - // Start stream and verify connection - await stream.start(); - expect(stream.state.status).toBe('connecting'); +const mockClient = createMockStreamClient(); +const stream = mockClient.createRenderStream({ + url: "https://test.example/stream", }); ``` -### Broker Event Simulation +## Simulate broker events -Utilities to simulate broker Server-Sent Events (SSE) for stream management: +```ts +import { simulateBrokerEvent } from "@open-game-system/stream-kit-testing"; -```typescript -import { simulateBrokerEvent } from '@open-game-system/stream-kit-testing'; - -test('handles broker peer assignment', async () => { - const mockClient = createMockStreamClient(); - const stream = mockClient.createRenderStream({ - url: 'https://test.com/stream' - }); - - await stream.start(); - expect(stream.state.status).toBe('connecting'); - - // Simulate broker assigning a peer - simulateBrokerEvent(stream, { - type: 'peer_assigned', - peerId: 'render-node-1', - connectionDetails: { - iceServers: [{ urls: 'stun:stun.example.com' }] - } - }); - - // Assert stream moves to connected state - expect(stream.state.status).toBe('connected'); -}); - -test('handles broker error scenarios', async () => { - const mockClient = createMockStreamClient(); - const stream = mockClient.createRenderStream({ - url: 'https://test.com/stream' - }); - - await stream.start(); - - // Simulate broker indicating node failure - simulateBrokerEvent(stream, { - type: 'node_failure', - reason: 'render_node_crashed' - }); - - expect(stream.state.status).toBe('error'); - expect(stream.state.error?.code).toBe('NODE_FAILURE'); +simulateBrokerEvent(stream, { + type: "peer_assigned", + peerId: "render-node-1", + connectionDetails: { + iceServers: [{ urls: "stun:stun.example.com" }], + }, }); ``` -### Stream State Assertions +## Assertions -Helper functions to assert stream states and transitions: - -```typescript -import { +```ts +import { assertStreamConnected, assertStreamDisconnected, - waitForStreamState -} from '@open-game-system/stream-kit-testing'; - -test('stream transitions through expected states', async () => { - const mockClient = createMockStreamClient(); - const stream = mockClient.createRenderStream({ - url: 'https://test.com/stream' - }); - - await stream.start(); - - // Wait for connecting state - await waitForStreamState(stream, 'connecting'); - - // Simulate successful peer assignment - simulateBrokerEvent(stream, { - type: 'peer_assigned', - peerId: 'render-node-1', - connectionDetails: { - iceServers: [{ urls: 'stun:stun.example.com' }] - } - }); - - // Assert connected state - assertStreamConnected(stream); - - // Simulate stream end - simulateBrokerEvent(stream, { - type: 'stream_ended', - reason: 'session_timeout' - }); - - assertStreamDisconnected(stream); -}); -``` - -### Testing React Components - -When using with React components (requires `@open-game-system/stream-kit-react`): - -```typescript -import { render, screen } from '@testing-library/react'; -import { createMockStreamClient } from '@open-game-system/stream-kit-testing'; -import { StreamProvider } from '@open-game-system/stream-kit-react'; - -test('StreamView component renders stream correctly', () => { - const mockClient = createMockStreamClient(); - - render( - - - - ); - - // Assert video element is present - expect(screen.getByTestId('stream-video')).toBeInTheDocument(); -}); -``` - -## API Reference - -### MockStreamClient - -```typescript -interface MockStreamClient { - createRenderStream: (params: CreateRenderStreamParams) => RenderStream; -} -``` - -### Broker Event Types + waitForStreamState, +} from "@open-game-system/stream-kit-testing"; -```typescript -type BrokerEvent = - | { type: 'peer_assigned'; peerId: string; connectionDetails: RTCConfiguration } - | { type: 'node_failure'; reason: string } - | { type: 'stream_ended'; reason: string } - | { type: 'quality_change'; settings: StreamQualitySettings }; - -interface StreamQualitySettings { - resolution: '720p' | '1080p' | '1440p' | '2160p'; - bitrate: number; - fps: number; -} +await waitForStreamState(stream, "connecting"); +assertStreamConnected(stream); ``` -### Assertion Functions +## React usage -```typescript -function assertStreamConnected(stream: RenderStream): void; -function assertStreamDisconnected(stream: RenderStream): void; -function assertStreamError(stream: RenderStream, code?: string): void; -function waitForStreamState(stream: RenderStream, status: StreamStatus): Promise; -``` +```tsx +import { StreamProvider } from "@open-game-system/stream-kit-react"; +import { createMockStreamClient } from "@open-game-system/stream-kit-testing"; -### Simulation Functions +const mockClient = createMockStreamClient(); +const stream = mockClient.createRenderStream({ url: "https://test.example/stream" }); -```typescript -function simulateBrokerEvent(stream: RenderStream, event: BrokerEvent): void; + + +; ``` -## Contributing +## Notes -Please see our [Contributing Guide](../../CONTRIBUTING.md) for details on how to contribute to this package. +- These helpers mirror the current package layer, not the full Cloudflare example runtime. +- They are most useful when testing `RenderStream` consumers and React bindings. ## License -MIT License \ No newline at end of file +MIT diff --git a/packages/stream-kit-testing/package.json b/packages/stream-kit-testing/package.json index 7683711..6145b21 100644 --- a/packages/stream-kit-testing/package.json +++ b/packages/stream-kit-testing/package.json @@ -14,14 +14,14 @@ "@open-game-system/stream-kit-types": "workspace:*" }, "devDependencies": { - "@types/node": "^20.0.0", - "@typescript-eslint/eslint-plugin": "^8.30.1", - "@typescript-eslint/parser": "^8.30.1", + "@types/node": "^20.19.13", + "@typescript-eslint/eslint-plugin": "^8.43.0", + "@typescript-eslint/parser": "^8.43.0", "@vitest/browser": "^1.6.1", "eslint": "^8.57.1", - "jsdom": "^24.0.0", - "typescript": "^5.0.0", - "vitest": "^1.6.0" + "jsdom": "^24.1.3", + "typescript": "^5.9.2", + "vitest": "^1.6.1" }, "peerDependencies": { "@open-game-system/stream-kit-web": "workspace:*" diff --git a/packages/stream-kit-types/README.md b/packages/stream-kit-types/README.md index cedea02..267dc9d 100644 --- a/packages/stream-kit-types/README.md +++ b/packages/stream-kit-types/README.md @@ -1,34 +1,30 @@ # @open-game-system/stream-kit-types -Core type definitions for the Open Game System (OGS) Cloud Rendering service (`stream-kit`). +Shared TypeScript types for the Stream Kit workspace. -## Overview +## What This Package Is -This package provides TypeScript type definitions used across the `stream-kit` ecosystem. It defines the core interfaces and types for: +This package holds the shared interfaces used across the current Stream Kit packages, including: -- Stream configuration and options -- State management and events -- WebRTC signaling and connection management -- Server-side session state -- JSON patch operations for state synchronization +- render options +- stream session metadata +- client-side stream state +- input event payloads +- server-side stream session state + +It is the lowest-level package in the workspace and contains no runtime behavior. ## Installation ```bash -npm install @open-game-system/stream-kit-types -# or pnpm add @open-game-system/stream-kit-types -# or -yarn add @open-game-system/stream-kit-types ``` -## Key Types - -### RenderOptions +## Main Types -Configuration options for stream quality and behavior: +### `RenderOptions` -```typescript +```ts interface RenderOptions { resolution?: "720p" | "1080p" | "1440p" | "4k" | string; targetFps?: number; @@ -38,11 +34,9 @@ interface RenderOptions { } ``` -### StreamState - -Real-time state of a streaming session: +### `StreamState` -```typescript +```ts interface StreamState { status: "initializing" | "connecting" | "streaming" | "reconnecting" | "error" | "ended"; latency?: number; @@ -54,52 +48,53 @@ interface StreamState { } ``` -### StreamEvent & InputStreamEvent +### `StreamSession` -Event structures for client-server communication: - -```typescript -interface StreamEvent { - type: string; - payload?: any; +```ts +interface StreamSession { + sessionId: string; + status: StreamState["status"]; + signalingUrl?: string; + iceServers?: RTCIceServer[]; + estimatedStartTime?: number; + region?: string; + error?: string; } - -type InputStreamEvent = StreamEvent & { - type: "interaction" | "command"; - data: { - action?: string; - position?: { x: number; y: number }; - // ... other properties - }; -}; ``` -## Usage +### `InputStreamEvent` -Import types as needed in your TypeScript code: +```ts +type InputStreamEvent = + | { type: "interaction"; data: { action: string; position?: { x: number; y: number } } } + | { type: "command"; data: { command: string; args?: any[] } }; +``` -```typescript -import type { - RenderOptions, - StreamState, - StreamEvent -} from '@open-game-system/stream-kit-types'; +## Usage -function configureStream(options: RenderOptions) { - // Your implementation -} +```ts +import type { + RenderOptions, + StreamSession, + StreamState, +} from "@open-game-system/stream-kit-types"; -function handleStateChange(state: StreamState) { - // Your implementation +function handleState(state: StreamState) { + console.log(state.status); } ``` +## Notes + +- These types reflect the current package layer, not the final OGS SDK surface. +- Some types still model the older broker-style API used by `stream-kit-web`. + ## Related Packages -- `@open-game-system/stream-kit-web`: Core client implementation -- `@open-game-system/stream-kit-react`: React components and hooks -- `@open-game-system/stream-kit-server`: Server-side implementation +- [`@open-game-system/stream-kit-web`](../stream-kit-web/README.md) +- [`@open-game-system/stream-kit-react`](../stream-kit-react/README.md) +- [`@open-game-system/stream-kit-server`](../stream-kit-server/README.md) ## License -MIT License \ No newline at end of file +MIT diff --git a/packages/stream-kit-types/package.json b/packages/stream-kit-types/package.json index 5f968c3..15a97cf 100644 --- a/packages/stream-kit-types/package.json +++ b/packages/stream-kit-types/package.json @@ -22,10 +22,10 @@ "fast-json-patch": "^3.1.1" }, "devDependencies": { - "rimraf": "^5.0.0", - "tsup": "^8.0.0", - "typescript": "^5.4.5", - "vitest": "^1.0.0" + "rimraf": "^5.0.10", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "vitest": "^1.6.1" }, "peerDependencies": { "typescript": ">=4.5.0" @@ -42,4 +42,4 @@ ], "author": "OpenGameSystem", "license": "MIT" -} \ No newline at end of file +} \ No newline at end of file diff --git a/packages/stream-kit-web/README.md b/packages/stream-kit-web/README.md index b838e58..c792a60 100644 --- a/packages/stream-kit-web/README.md +++ b/packages/stream-kit-web/README.md @@ -1,159 +1,90 @@ # @open-game-system/stream-kit-web -Core client implementation for the Open Game System (OGS) Cloud Rendering service. +Low-level browser client primitives for the Stream Kit package layer. -## Overview +## What This Package Is -This package provides the foundational client-side implementation for integrating cloud-rendered streams into web applications. It handles: +This package exposes: -- Stream session management -- WebRTC connection setup and management -- State synchronization -- Input event forwarding -- Automatic reconnection and error handling +- `createStreamClient()` +- a broker-style `StreamClient` +- a `RenderStream` abstraction that owns a video element, stream state, and WebRTC setup + +It is a low-level client package. It does not start Cloudflare containers by itself, and it is not yet the polished OGS app SDK described in the repo docs. ## Installation ```bash -npm install @open-game-system/stream-kit-web @open-game-system/stream-kit-types -# or pnpm add @open-game-system/stream-kit-web @open-game-system/stream-kit-types -# or -yarn add @open-game-system/stream-kit-web @open-game-system/stream-kit-types ``` -## Usage +## Current API -### Basic Example +### `createStreamClient` -```typescript -import { createStreamClient } from '@open-game-system/stream-kit-web'; +```ts +import { createStreamClient } from "@open-game-system/stream-kit-web"; -// Create a client instance const client = createStreamClient({ - brokerUrl: 'https://api.opengame.org/stream', - getAuthToken: async () => 'your-auth-token' // Optional -}); - -// Create a render stream instance -const stream = client.createRenderStream({ - url: 'https://your-game.com/render/scene', - renderOptions: { - resolution: '1080p', - quality: 'high' - }, - autoConnect: true // Start streaming immediately -}); - -// Subscribe to state changes -stream.subscribe((state) => { - console.log('Stream state:', state); + brokerUrl: "https://api.example.com", }); - -// Get the video element to insert into your UI -const videoElement = stream.getVideoElement(); -if (videoElement) { - document.getElementById('stream-container')?.appendChild(videoElement); -} - -// Send input events when needed -stream.send({ - type: 'interaction', - data: { - action: 'click', - position: { x: 100, y: 200 } - } -}); - -// Clean up when done -stream.destroy(); ``` -### Advanced Usage +The returned client currently assumes a broker-style HTTP API with endpoints like: -#### Custom WebRTC Configuration +- `POST /stream/session` +- `DELETE /stream/session/:id` +- `POST /stream/session/:id/input` +- `PATCH /stream/session/:id` -```typescript -const stream = client.createRenderStream({ - url: 'https://your-game.com/render/scene', - renderOptions: { - resolution: '1080p', - quality: 'high', - priority: 'latency' - }, - initialData: { - scene: 'world-1', - playerPosition: { x: 0, y: 0, z: 0 } - } -}); +### `createRenderStream` -// Update stream configuration dynamically -await stream.update({ +```ts +const stream = client.createRenderStream({ + url: "https://your-game.com/render/world", renderOptions: { - resolution: '720p', // Downgrade on poor connection - quality: 'medium' + resolution: "1080p", + quality: "high", }, - sceneData: { - playerPosition: { x: 10, y: 0, z: 5 } - } }); ``` -#### Error Handling - -```typescript -stream.subscribe((state) => { - if (state.status === 'error') { - console.error('Stream error:', state.errorMessage); - if (state.errorCode === 'connection-lost') { - // Handle reconnection - stream.start(); - } - } +### State subscription + +```ts +const unsubscribe = stream.subscribe((state) => { + console.log(state.status); }); ``` -## API Reference - -### StreamClient +### Mounting the video element -Core client for interacting with the OGS Stream API: - -```typescript -interface StreamClient { - requestStream: (params: RequestStreamParams) => Promise; - endStream: (sessionId: string) => Promise; - sendEvent: (sessionId: string, event: InputStreamEvent) => Promise; - updateStream: (sessionId: string, updates: StreamUpdates) => Promise; - createRenderStream: (params: CreateRenderStreamParams) => RenderStream; +```ts +const video = stream.getVideoElement(); +if (video) { + document.getElementById("stream-root")?.appendChild(video); } ``` -### RenderStream - -High-level stream management interface: - -```typescript -interface RenderStream { - readonly id: string; - readonly url: string; - readonly state: StreamState; - start: () => Promise; - end: () => Promise; - send: (event: InputStreamEvent) => void; - update: (updates: StreamUpdates) => Promise; - subscribe: (listener: (state: StreamState) => void) => () => void; - getVideoElement: () => HTMLVideoElement | null; - destroy: () => void; -} +### Cleanup + +```ts +await stream.end(); +stream.destroy(); ``` +## Notes + +- The current implementation still reflects an older broker/signaling model. +- The working end-to-end Cloudflare runtime in `examples/bun-stream-server/` does not directly consume this package yet. +- Treat this package as low-level and experimental relative to the deployed example. + ## Related Packages -- `@open-game-system/stream-kit-react`: React components and hooks -- `@open-game-system/stream-kit-types`: TypeScript type definitions -- `@open-game-system/stream-kit-server`: Server-side implementation +- [`@open-game-system/stream-kit-types`](../stream-kit-types/README.md) +- [`@open-game-system/stream-kit-react`](../stream-kit-react/README.md) +- [`@open-game-system/stream-kit-testing`](../stream-kit-testing/README.md) ## License -MIT License \ No newline at end of file +MIT diff --git a/packages/stream-kit-web/package.json b/packages/stream-kit-web/package.json index 3c8fac4..1132ec3 100644 --- a/packages/stream-kit-web/package.json +++ b/packages/stream-kit-web/package.json @@ -1,50 +1,42 @@ { "name": "@open-game-system/stream-kit-web", - "version": "0.0.0", - "description": "Core web client library for OGS Cloud Rendering (Stream Kit)", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "sideEffects": false, - "files": [ - "dist", - "README.md", - "LICENSE" - ], + "version": "0.1.0", + "description": "Browser client for streaming with PeerJS", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts", - "dev": "tsup src/index.ts --format cjs,esm --dts --watch", - "clean": "rimraf dist", - "typecheck": "tsc --noEmit", + "build": "tsup", + "dev": "tsup --watch", "test": "vitest run", - "test:watch": "vitest", - "test:coverage": "vitest run --coverage" + "test:watch": "vitest --watch", + "test:coverage": "vitest --coverage", + "typecheck": "tsc --noEmit", + "clean": "rm -rf dist" }, "dependencies": { - "@open-game-system/stream-kit-types": "workspace:*" + "@open-game-system/stream-kit-types": "workspace:*", + "peerjs": "^1.5.5" }, "devDependencies": { - "rimraf": "^5.0.0", - "tsup": "^8.0.0", - "typescript": "^5.4.5", - "vitest": "^1.6.0", - "@vitest/coverage-v8": "^1.6.0", - "jsdom": "^24.0.0" - }, - "peerDependencies": { - "typescript": ">=4.5.0" - }, - "publishConfig": { - "access": "public" + "@types/node": "^22.18.1", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "vitest": "^2.1.9" }, "keywords": [ - "typescript", - "web", - "stream-kit", - "cloud-rendering", "webrtc", - "ogs" + "peerjs", + "streaming", + "browser" ], - "author": "OpenGameSystem", + "author": "", "license": "MIT" -} \ No newline at end of file +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2addfad..5763fd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: devDependencies: pnpm: - specifier: ^9.5.0 - version: 9.5.0 + specifier: ^9.15.9 + version: 9.15.9 turbo: - specifier: ^2.0.9 - version: 2.0.9 + specifier: ^2.5.6 + version: 2.5.6 typescript: - specifier: ^5.4.5 - version: 5.4.5 + specifier: ^5.9.2 + version: 5.9.2 examples/basic-react-demo: dependencies: @@ -30,39 +30,39 @@ importers: specifier: workspace:* version: link:../../packages/stream-kit-web react: - specifier: ^18.2.0 - version: 18.2.0 + specifier: ^18.3.1 + version: 18.3.1 react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) devDependencies: '@testing-library/jest-dom': - specifier: ^6.4.2 - version: 6.4.2(@types/jest@29.5.14)(vitest@1.6.0(@types/node@22.14.1)(jsdom@23.0.1)) + specifier: ^6.8.0 + version: 6.8.0 '@testing-library/react': - specifier: ^14.2.1 - version: 14.2.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^14.3.1 + version: 14.3.1(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.0) '@types/react': - specifier: ^18.2.37 - version: 18.2.37 + specifier: ^18.3.24 + version: 18.3.24 '@types/react-dom': - specifier: ^18.2.15 - version: 18.2.15 + specifier: ^18.3.7 + version: 18.3.7(@types/react@18.3.24) '@types/testing-library__jest-dom': - specifier: ^5.14.5 - version: 5.14.5 + specifier: ^5.14.9 + version: 5.14.9 '@typescript-eslint/eslint-plugin': - specifier: ^8.30.1 - version: 8.30.1(@typescript-eslint/parser@8.30.1(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1)(typescript@5.4.5) + specifier: ^8.43.0 + version: 8.43.0(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/parser': - specifier: ^8.30.1 - version: 8.30.1(eslint@8.57.1)(typescript@5.4.5) + specifier: ^8.43.0 + version: 8.43.0(eslint@8.57.1)(typescript@5.9.2) '@vitejs/plugin-react': - specifier: ^4.2.0 - version: 4.2.0(vite@5.0.0(@types/node@22.14.1)) + specifier: ^4.7.0 + version: 4.7.0(vite@5.4.20(@types/node@24.3.1)) eslint: specifier: ^8.57.1 version: 8.57.1 @@ -70,35 +70,38 @@ importers: specifier: ^7.37.5 version: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: - specifier: ^4.6.0 - version: 4.6.0(eslint@8.57.1) + specifier: ^4.6.2 + version: 4.6.2(eslint@8.57.1) eslint-plugin-react-refresh: - specifier: ^0.4.4 - version: 0.4.4(eslint@8.57.1) + specifier: ^0.4.20 + version: 0.4.20(eslint@8.57.1) jsdom: - specifier: ^23.0.1 - version: 23.0.1 + specifier: ^23.2.0 + version: 23.2.0 typescript: - specifier: ^5.2.2 - version: 5.4.5 + specifier: ^5.9.2 + version: 5.9.2 vite: - specifier: ^5.0.0 - version: 5.0.0(@types/node@22.14.1) + specifier: ^5.4.20 + version: 5.4.20(@types/node@24.3.1) vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@22.14.1)(jsdom@23.0.1) + specifier: ^1.6.1 + version: 1.6.1(@types/node@24.3.1)(@vitest/browser@1.6.1)(jsdom@23.2.0) examples/bun-stream-server: devDependencies: + '@playwright/test': + specifier: ^1.52.0 + version: 1.58.2 '@types/bun': - specifier: ^1.2.10 - version: 1.2.10 + specifier: ^1.2.21 + version: 1.2.21(@types/react@18.3.24) '@types/node': - specifier: ^22.14.1 - version: 22.14.1 + specifier: ^22.18.1 + version: 22.18.1 wrangler: - specifier: https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-wrangler-8586 - version: https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-wrangler-8586(@cloudflare/workers-types@4.20250319.0) + specifier: 4.73.0 + version: 4.73.0 packages/stream-kit-react: dependencies: @@ -110,35 +113,35 @@ importers: version: link:../stream-kit-web devDependencies: '@testing-library/jest-dom': - specifier: ^6.0.0 - version: 6.4.2(@types/jest@29.5.14)(vitest@1.6.0(@types/node@22.14.1)(jsdom@24.0.0)) + specifier: ^6.8.0 + version: 6.8.0 '@testing-library/react': - specifier: ^16.0.0 - version: 16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/react': - specifier: ^18.2.0 - version: 18.2.37 + specifier: ^18.3.24 + version: 18.3.24 '@vitest/coverage-v8': - specifier: ^1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@22.14.1)(jsdom@24.0.0)) + specifier: ^1.6.1 + version: 1.6.1(vitest@1.6.1(@types/node@24.3.1)(@vitest/browser@1.6.1)(jsdom@24.1.3)) jsdom: - specifier: ^24.0.0 - version: 24.0.0 + specifier: ^24.1.3 + version: 24.1.3 react: - specifier: ^18.2.0 - version: 18.2.0 + specifier: ^18.3.1 + version: 18.3.1 rimraf: - specifier: ^5.0.0 - version: 5.0.5 + specifier: ^5.0.10 + version: 5.0.10 tsup: - specifier: ^8.0.0 - version: 8.0.2(postcss@8.5.3)(typescript@5.4.5) + specifier: ^8.5.0 + version: 8.5.0(postcss@8.5.6)(typescript@5.9.2)(yaml@2.7.1) typescript: - specifier: ^5.4.5 - version: 5.4.5 + specifier: ^5.9.2 + version: 5.9.2 vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@22.14.1)(jsdom@24.0.0) + specifier: ^1.6.1 + version: 1.6.1(@types/node@24.3.1)(@vitest/browser@1.6.1)(jsdom@24.1.3) packages/stream-kit-server: dependencies: @@ -152,39 +155,39 @@ importers: specifier: ^0.13.0 version: 0.13.0 peerjs: - specifier: ^1.5.4 - version: 1.5.4 + specifier: ^1.5.5 + version: 1.5.5 puppeteer: - specifier: 22.13.1 - version: 22.13.1(typescript@5.4.5) + specifier: 24.20.0 + version: 24.20.0(typescript@5.9.2) puppeteer-stream: specifier: 3.0.20 version: 3.0.20 devDependencies: '@types/node': - specifier: ^18.11.18 - version: 18.11.18 + specifier: ^18.19.124 + version: 18.19.124 '@types/peerjs': specifier: ^1.1.0 version: 1.1.0 '@types/ws': - specifier: ^8.5.11 - version: 8.5.11 + specifier: ^8.18.1 + version: 8.18.1 '@vitest/coverage-v8': - specifier: ^1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@18.11.18)(jsdom@24.0.0)) + specifier: ^1.6.1 + version: 1.6.1(vitest@1.6.1(@types/node@18.19.124)(@vitest/browser@1.6.1)(jsdom@24.1.3)) rimraf: - specifier: ^5.0.5 - version: 5.0.5 + specifier: ^5.0.10 + version: 5.0.10 tsup: - specifier: ^8.0.2 - version: 8.0.2(postcss@8.5.3)(typescript@5.4.5) + specifier: ^8.5.0 + version: 8.5.0(postcss@8.5.6)(typescript@5.9.2)(yaml@2.7.1) typescript: - specifier: ^5.3.3 - version: 5.4.5 + specifier: ^5.9.2 + version: 5.9.2 vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@18.11.18)(jsdom@24.0.0) + specifier: ^1.6.1 + version: 1.6.1(@types/node@18.19.124)(@vitest/browser@1.6.1)(jsdom@24.1.3) packages/stream-kit-testing: dependencies: @@ -196,29 +199,29 @@ importers: version: link:../stream-kit-web devDependencies: '@types/node': - specifier: ^20.0.0 - version: 20.0.0 + specifier: ^20.19.13 + version: 20.19.13 '@typescript-eslint/eslint-plugin': - specifier: ^8.30.1 - version: 8.30.1(@typescript-eslint/parser@8.30.1(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1)(typescript@5.4.5) + specifier: ^8.43.0 + version: 8.43.0(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/parser': - specifier: ^8.30.1 - version: 8.30.1(eslint@8.57.1)(typescript@5.4.5) + specifier: ^8.43.0 + version: 8.43.0(eslint@8.57.1)(typescript@5.9.2) '@vitest/browser': specifier: ^1.6.1 - version: 1.6.1(vitest@1.6.0) + version: 1.6.1(playwright@1.58.2)(vitest@1.6.1) eslint: specifier: ^8.57.1 version: 8.57.1 jsdom: - specifier: ^24.0.0 - version: 24.0.0 + specifier: ^24.1.3 + version: 24.1.3 typescript: - specifier: ^5.0.0 - version: 5.4.5 + specifier: ^5.9.2 + version: 5.9.2 vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@20.0.0)(@vitest/browser@1.6.1)(jsdom@24.0.0) + specifier: ^1.6.1 + version: 1.6.1(@types/node@20.19.13)(@vitest/browser@1.6.1)(jsdom@24.1.3) packages/stream-kit-types: dependencies: @@ -227,514 +230,675 @@ importers: version: 3.1.1 devDependencies: rimraf: - specifier: ^5.0.0 - version: 5.0.5 + specifier: ^5.0.10 + version: 5.0.10 tsup: - specifier: ^8.0.0 - version: 8.0.2(postcss@8.5.3)(typescript@5.4.5) + specifier: ^8.5.0 + version: 8.5.0(postcss@8.5.6)(typescript@5.9.2)(yaml@2.7.1) typescript: - specifier: ^5.4.5 - version: 5.4.5 + specifier: ^5.9.2 + version: 5.9.2 vitest: - specifier: ^1.0.0 - version: 1.6.0(@types/node@22.14.1)(jsdom@24.0.0) + specifier: ^1.6.1 + version: 1.6.1(@types/node@24.3.1)(@vitest/browser@1.6.1)(jsdom@24.1.3) packages/stream-kit-web: dependencies: '@open-game-system/stream-kit-types': specifier: workspace:* version: link:../stream-kit-types + peerjs: + specifier: ^1.5.5 + version: 1.5.5 devDependencies: - '@vitest/coverage-v8': - specifier: ^1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@22.14.1)(jsdom@24.0.0)) - jsdom: - specifier: ^24.0.0 - version: 24.0.0 - rimraf: - specifier: ^5.0.0 - version: 5.0.5 + '@types/node': + specifier: ^22.18.1 + version: 22.18.1 tsup: - specifier: ^8.0.0 - version: 8.0.2(postcss@8.5.3)(typescript@5.4.5) + specifier: ^8.5.0 + version: 8.5.0(postcss@8.5.6)(typescript@5.9.2)(yaml@2.7.1) typescript: - specifier: ^5.4.5 - version: 5.4.5 + specifier: ^5.9.2 + version: 5.9.2 vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@22.14.1)(jsdom@24.0.0) + specifier: ^2.1.9 + version: 2.1.9(@types/node@22.18.1)(jsdom@24.1.3) packages: - '@adobe/css-tools@4.4.2': - resolution: {integrity: sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==} + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@asamuzakjp/css-color@3.1.2': - resolution: {integrity: sha512-nwgc7jPn3LpZ4JWsoHtuwBsad1qSSLDDX634DdG0PBJofIuIEtSWk4KkRmuXyu178tjuHAbwiMNNzwqIyLYxZw==} + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + '@asamuzakjp/dom-selector@2.0.2': + resolution: {integrity: sha512-x1KXOatwofR6ZAYzXRBL5wrdV0vwNxlTCK9NCuLqAzQYARqGcvFwiJA6A1ERuh+dgeA4Dxm3JBYictIes+SqUQ==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.4': + resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.8': - resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + '@babel/core@7.28.4': + resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.10': - resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.27.0': - resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.0': - resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.26.5': - resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.0': - resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.27.0': - resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.25.9': - resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.25.9': - resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.27.0': - resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} - '@babel/template@7.27.0': - resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.27.0': - resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + '@babel/traverse@7.28.4': + resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.27.0': - resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@cloudflare/kv-asset-handler@https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-cloudflare-kv-asset-handler-8586': - resolution: {tarball: https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-cloudflare-kv-asset-handler-8586} - version: 0.0.0-v1a1c5d714 + '@cloudflare/kv-asset-handler@0.4.2': + resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} - '@cloudflare/unenv-preset@https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-cloudflare-unenv-preset-8586': - resolution: {tarball: https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-cloudflare-unenv-preset-8586} - version: 0.0.0-v1a1c5d714 + '@cloudflare/unenv-preset@2.15.0': + resolution: {integrity: sha512-EGYmJaGZKWl+X8tXxcnx4v2bOZSjQeNI5dWFeXivgX9+YCT69AkzHHwlNbVpqtEUTbew8eQurpyOpeN8fg00nw==} peerDependencies: - unenv: 2.0.0-rc.15 - workerd: ^1.20250311.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0 peerDependenciesMeta: workerd: optional: true - '@cloudflare/workerd-darwin-64@1.20250319.0': - resolution: {integrity: sha512-8B08kYAp1rEgXRe+YV3uCAGYa65KS8V/pajmgh5U4yULLmHryd4OPo8wf3RYl4uk5ZNbtUKR1GUNLkIraTL6Rw==} + '@cloudflare/workerd-darwin-64@1.20260312.1': + resolution: {integrity: sha512-HUAtDWaqUduS6yasV6+NgsK7qBpP1qGU49ow/Wb117IHjYp+PZPUGReDYocpB4GOMRoQlvdd4L487iFxzdARpw==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20250319.0': - resolution: {integrity: sha512-UW1c15oFYRPxwt4qEQufA/XlK5AnbVJFs7PwXo2suLXhxAdTZUKk0Mg1DgDTN65wmqQimRt4ayLVbfxFpzt0bA==} + '@cloudflare/workerd-darwin-arm64@1.20260312.1': + resolution: {integrity: sha512-DOn7TPTHSxJYfi4m4NYga/j32wOTqvJf/pY4Txz5SDKWIZHSTXFyGz2K4B+thoPWLop/KZxGoyTv7db0mk/qyw==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20250319.0': - resolution: {integrity: sha512-oYrTq/FP74IxaEwqHZep8sPoy5btrb8x9ubt6aYy+A1s8IHqma3bYzDmRJ8AMDl9d5+ASFqkAqB/Cj8HN1agPA==} + '@cloudflare/workerd-linux-64@1.20260312.1': + resolution: {integrity: sha512-TdkIh3WzPXYHuvz7phAtFEEvAxvFd30tHrm4gsgpw0R0F5b8PtoM3hfL2uY7EcBBWVYUBtkY2ahDYFfufnXw/g==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20250319.0': - resolution: {integrity: sha512-n9Qy+iA6SQSL3dRLXlkTQEHP65/i6Mk4UOFh67BOlrFJlov+/u77pyGG4koPLcuts0SnGxA0eDNlua6CN/GGAg==} + '@cloudflare/workerd-linux-arm64@1.20260312.1': + resolution: {integrity: sha512-kNauZhL569Iy94t844OMwa1zP6zKFiL3xiJ4tGLS+TFTEfZ3pZsRH6lWWOtkXkjTyCmBEOog0HSEKjIV4oAffw==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20250319.0': - resolution: {integrity: sha512-MUYzPZiz3wbLtHjfc0RdO0tETTDJF9OcRPNzw8RpWba98Z1uhMX2hQ5gNQNgQJ+Y5TDMlcKHZVIJU/5E7/qcZw==} + '@cloudflare/workerd-windows-64@1.20260312.1': + resolution: {integrity: sha512-5dBrlSK+nMsZy5bYQpj8t9iiQNvCRlkm9GGvswJa9vVU/1BNO4BhJMlqOLWT24EmFyApZ+kaBiPJMV8847NDTg==} engines: {node: '>=16'} cpu: [x64] os: [win32] - '@cloudflare/workers-types@4.20250319.0': - resolution: {integrity: sha512-TTg1WZZuWJomzU3g1TvqE/WWI3zpTu1K+RsJvk6zxEgjhONN2PzqUUqKVYOQBk4/p7d+v7h6wz2gX3Ke7Arm7g==} - '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@csstools/color-helpers@5.0.2': - resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} - '@csstools/css-calc@2.1.2': - resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==} + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.4 - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-color-parser@3.0.8': - resolution: {integrity: sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==} + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.4 - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-parser-algorithms@3.0.4': - resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-tokenizer@3.0.3': - resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@emnapi/runtime@1.4.3': - resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + '@emnapi/runtime@1.9.0': + resolution: {integrity: sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==} - '@esbuild/aix-ppc64@0.19.12': - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.24.2': - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.19.12': - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.24.2': - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.19.12': - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.24.2': - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.19.12': - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.24.2': - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.19.12': - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.24.2': - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.19.12': - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.24.2': - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.19.12': - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.24.2': - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.19.12': - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.2': - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.19.12': - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.24.2': - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.19.12': - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.24.2': - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.19.12': - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.24.2': - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.19.12': - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.24.2': - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.19.12': - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.24.2': - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.19.12': - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.24.2': - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.19.12': - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.24.2': - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.19.12': - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.24.2': - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.19.12': - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.24.2': - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.24.2': - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.19.12': - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.24.2': - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.24.2': - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.19.12': - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.2': - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.19.12': - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.24.2': - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.19.12': - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.24.2': - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.19.12': - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.24.2': - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.19.12': - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.24.2': - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.6.1': - resolution: {integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==} + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -751,10 +915,6 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -768,107 +928,139 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -881,35 +1073,49 @@ packages: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - '@jest/expect-utils@29.7.0': - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/diff-sequences@30.0.1': + resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.1.2': + resolution: {integrity: sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.0.1': + resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jest/types@29.6.3': - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/schemas@30.0.5': + resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} + '@jest/types@30.0.5': + resolution: {integrity: sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -934,122 +1140,149 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.58.2': + resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} + engines: {node: '>=18'} + hasBin: true + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@puppeteer/browsers@2.10.0': - resolution: {integrity: sha512-HdHF4rny4JCvIcm7V1dpvpctIGqM3/Me255CB44vW7hDG1zYMmcBMjpNqZEDxdCfXGLkx5kP0+Jz5DUS+ukqtA==} - engines: {node: '>=18'} - hasBin: true + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@puppeteer/browsers@2.2.4': - resolution: {integrity: sha512-BdG2qiI1dn89OTUUsx2GZSpUzW+DRffR1wlMJyKxVHYrhnKoELSDxDd+2XImUkuWPEKk76H5FcM/gPFrEK1Tfw==} + '@puppeteer/browsers@2.10.9': + resolution: {integrity: sha512-kUGHwABarVhvMP+zhW5zvDA7LmGcd4TwrTEBwcTQic5EebUqaK5NjC0UXLJepIFVGsr2N/Z8NJQz2JYGo1ZwxA==} engines: {node: '>=18'} hasBin: true - '@rollup/rollup-android-arm-eabi@4.40.0': - resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==} + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.50.1': + resolution: {integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.40.0': - resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==} + '@rollup/rollup-android-arm64@4.50.1': + resolution: {integrity: sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.40.0': - resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==} + '@rollup/rollup-darwin-arm64@4.50.1': + resolution: {integrity: sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.40.0': - resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==} + '@rollup/rollup-darwin-x64@4.50.1': + resolution: {integrity: sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.40.0': - resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==} + '@rollup/rollup-freebsd-arm64@4.50.1': + resolution: {integrity: sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.40.0': - resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==} + '@rollup/rollup-freebsd-x64@4.50.1': + resolution: {integrity: sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.40.0': - resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': + resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.40.0': - resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} + '@rollup/rollup-linux-arm-musleabihf@4.50.1': + resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.40.0': - resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} + '@rollup/rollup-linux-arm64-gnu@4.50.1': + resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.40.0': - resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} + '@rollup/rollup-linux-arm64-musl@4.50.1': + resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.40.0': - resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': + resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': - resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} + '@rollup/rollup-linux-ppc64-gnu@4.50.1': + resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.40.0': - resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} + '@rollup/rollup-linux-riscv64-gnu@4.50.1': + resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.40.0': - resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} + '@rollup/rollup-linux-riscv64-musl@4.50.1': + resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.40.0': - resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} + '@rollup/rollup-linux-s390x-gnu@4.50.1': + resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.40.0': - resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} + '@rollup/rollup-linux-x64-gnu@4.50.1': + resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.40.0': - resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} + '@rollup/rollup-linux-x64-musl@4.50.1': + resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.40.0': - resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} + '@rollup/rollup-openharmony-arm64@4.50.1': + resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.50.1': + resolution: {integrity: sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.40.0': - resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==} + '@rollup/rollup-win32-ia32-msvc@4.50.1': + resolution: {integrity: sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.40.0': - resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==} + '@rollup/rollup-win32-x64-msvc@4.50.1': + resolution: {integrity: sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==} cpu: [x64] os: [win32] '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sinclair/typebox@0.34.41': + resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} + + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.14': + resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==} + '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} @@ -1058,43 +1291,26 @@ packages: resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} engines: {node: '>=14'} - '@testing-library/jest-dom@6.4.2': - resolution: {integrity: sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==} + '@testing-library/jest-dom@6.8.0': + resolution: {integrity: sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - peerDependencies: - '@jest/globals': '>= 28' - '@types/bun': latest - '@types/jest': '>= 28' - jest: '>= 28' - vitest: '>= 0.32' - peerDependenciesMeta: - '@jest/globals': - optional: true - '@types/bun': - optional: true - '@types/jest': - optional: true - jest: - optional: true - vitest: - optional: true - '@testing-library/react@14.2.1': - resolution: {integrity: sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A==} + '@testing-library/react@14.3.1': + resolution: {integrity: sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==} engines: {node: '>=14'} peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - '@testing-library/react@16.0.0': - resolution: {integrity: sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ==} + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} engines: {node: '>=18'} peerDependencies: '@testing-library/dom': ^10.0.0 - '@types/react': ^18.0.0 - '@types/react-dom': ^18.0.0 - react: ^18.0.0 - react-dom: ^18.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': optional: true @@ -1122,14 +1338,14 @@ packages: '@types/babel__template@7.4.4': resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@types/babel__traverse@7.20.7': - resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - '@types/bun@1.2.10': - resolution: {integrity: sha512-eilv6WFM3M0c9ztJt7/g80BDusK98z/FrFwseZgT4bXCq2vPhXD4z8R3oddmAn+R/Nmz9vBn4kweJKmGTZj+lg==} + '@types/bun@1.2.21': + resolution: {integrity: sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A==} - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -1140,42 +1356,44 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - '@types/jest@29.5.14': - resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + '@types/jest@30.0.0': + resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==} - '@types/node@18.11.18': - resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} + '@types/node@18.19.124': + resolution: {integrity: sha512-hY4YWZFLs3ku6D2Gqo3RchTd9VRCcrjqp/I0mmohYeUVA5Y8eCXKJEasHxLAJVZRJuQogfd1GiJ9lgogBgKeuQ==} - '@types/node@20.0.0': - resolution: {integrity: sha512-cD2uPTDnQQCVpmRefonO98/PPijuOnnEy5oytWJFPY1N9aJCz2wJ5kSGWO+zJoed2cY2JxQh6yBuUq4vIn61hw==} + '@types/node@20.19.13': + resolution: {integrity: sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==} - '@types/node@22.14.1': - resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} + '@types/node@22.18.1': + resolution: {integrity: sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==} + + '@types/node@24.3.1': + resolution: {integrity: sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==} '@types/peerjs@1.1.0': resolution: {integrity: sha512-dVocsfYFg5QQuUB9OAxfrSvz4br4pyX+7M61ZJSRiYtE3NdayShk1p1Y8b9TmCj724TwHskVraeF7wyPl0rYcg==} deprecated: This is a stub types definition. peerjs provides its own type definitions, so you do not need this installed. - '@types/prop-types@15.7.14': - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - - '@types/react-dom@18.2.15': - resolution: {integrity: sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - '@types/react@18.2.37': - resolution: {integrity: sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==} + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 - '@types/scheduler@0.26.0': - resolution: {integrity: sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==} + '@types/react@18.3.24': + resolution: {integrity: sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/testing-library__jest-dom@5.14.5': - resolution: {integrity: sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==} + '@types/testing-library__jest-dom@5.14.9': + resolution: {integrity: sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==} - '@types/ws@8.5.11': - resolution: {integrity: sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -1186,61 +1404,73 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.30.1': - resolution: {integrity: sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==} + '@typescript-eslint/eslint-plugin@8.43.0': + resolution: {integrity: sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + '@typescript-eslint/parser': ^8.43.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.30.1': - resolution: {integrity: sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==} + '@typescript-eslint/parser@8.43.0': + resolution: {integrity: sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.43.0': + resolution: {integrity: sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.30.1': - resolution: {integrity: sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==} + '@typescript-eslint/scope-manager@8.43.0': + resolution: {integrity: sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.30.1': - resolution: {integrity: sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==} + '@typescript-eslint/tsconfig-utils@8.43.0': + resolution: {integrity: sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.43.0': + resolution: {integrity: sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.30.1': - resolution: {integrity: sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==} + '@typescript-eslint/types@8.43.0': + resolution: {integrity: sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.30.1': - resolution: {integrity: sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==} + '@typescript-eslint/typescript-estree@8.43.0': + resolution: {integrity: sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.30.1': - resolution: {integrity: sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==} + '@typescript-eslint/utils@8.43.0': + resolution: {integrity: sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.30.1': - resolution: {integrity: sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==} + '@typescript-eslint/visitor-keys@8.43.0': + resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitejs/plugin-react@4.2.0': - resolution: {integrity: sha512-+MHTH/e6H12kRp5HUkzOGqPMksezRMmW+TNzlh/QXfI8rRf6l2Z2yH/v12no1UvTwhZgEDMuQ7g7rrfMseU6FQ==} + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 '@vitest/browser@1.6.1': resolution: {integrity: sha512-9ZYW6KQ30hJ+rIfJoGH4wAub/KAb4YrFzX0kVLASvTm7nJWVC5EAv5SlzlXVl3h3DaUq5aqHlZl77nmOPnALUQ==} @@ -1257,54 +1487,71 @@ packages: webdriverio: optional: true - '@vitest/coverage-v8@1.6.0': - resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==} + '@vitest/coverage-v8@1.6.1': + resolution: {integrity: sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==} + peerDependencies: + vitest: 1.6.1 + + '@vitest/expect@1.6.1': + resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} peerDependencies: - vitest: 1.6.0 + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true - '@vitest/expect@1.6.0': - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} - '@vitest/runner@1.6.0': - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + '@vitest/runner@1.6.1': + resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} - '@vitest/snapshot@1.6.0': - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} - '@vitest/spy@1.6.0': - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + '@vitest/snapshot@1.6.1': + resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} - '@vitest/utils@1.6.0': - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@1.6.1': + resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} '@vitest/utils@1.6.1': resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} - acorn-walk@8.3.4: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true - agent-base@7.1.3: - resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} ajv@6.12.6: @@ -1314,8 +1561,8 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} ansi-styles@4.3.0: @@ -1326,17 +1573,13 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1354,14 +1597,10 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} - array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -1382,12 +1621,13 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} - as-table@1.0.55: - resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} - assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types@0.13.4: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} @@ -1403,17 +1643,22 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - b4a@1.6.7: - resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + b4a@1.7.1: + resolution: {integrity: sha512-ZovbrBV0g6JxK5cGUF1Suby1vLfKjv4RWi8IxoaO/Mon8BDD9I21RxjHFtgQ+kskJqLAVyQZly3uMBui+vhc8Q==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - bare-events@2.5.4: - resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} + bare-events@2.6.1: + resolution: {integrity: sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==} - bare-fs@4.1.2: - resolution: {integrity: sha512-8wSeOia5B7LwD4+h465y73KOdj5QHsbbuoUfPBi+pXgFJIPuG7SsiOdJuijWMyfid49eD+WivpfY7KT8gbAzBA==} + bare-fs@4.4.4: + resolution: {integrity: sha512-Q8yxM1eLhJfuM7KXVP3zjhBvtMJCYRByoTT+wHXjpdMELv0xICFJX+1w4c7csa+WZEOsq4ItJ4RGwvzid6m/dw==} engines: {bare: '>=1.16.0'} peerDependencies: bare-buffer: '*' @@ -1421,15 +1666,15 @@ packages: bare-buffer: optional: true - bare-os@3.6.1: - resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + bare-os@3.6.2: + resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} engines: {bare: '>=1.14.0'} bare-path@3.0.0: resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - bare-stream@2.6.5: - resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} + bare-stream@2.7.0: + resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} peerDependencies: bare-buffer: '*' bare-events: '*' @@ -1439,49 +1684,51 @@ packages: bare-events: optional: true - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bare-url@2.2.2: + resolution: {integrity: sha512-g+ueNGKkrjMazDG3elZO1pNs3HY5+mMmOet1jtKyhOaCnkLzitxf26z7hoAEkDNgdNmnc1KIlt/dw6Po6xZMpA==} + + baseline-browser-mapping@2.8.2: + resolution: {integrity: sha512-NvcIedLxrs9llVpX7wI+Jz4Hn9vJQkCPKrTaHIE0sW/Rj1iq6Fzby4NbyTZjQJNoypBXNaG7tEHkTgONZpwgxQ==} + hasBin: true basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.24.4: - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + browserslist@4.26.0: + resolution: {integrity: sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - bun-types@1.2.10: - resolution: {integrity: sha512-b5ITZMnVdf3m1gMvJHG+gIfeJHiQPJak0f7925Hxu6ZN5VKA8AGy4GZ4lM+Xkn6jtWxg5S3ldWvfmXdvnkp3GQ==} + bun-types@1.2.21: + resolution: {integrity: sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw==} + peerDependencies: + '@types/react': ^19 - bundle-require@4.2.1: - resolution: {integrity: sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==} + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: - esbuild: '>=0.17' + esbuild: '>=0.18' cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} @@ -1503,16 +1750,16 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001714: - resolution: {integrity: sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==} + caniuse-lite@1.0.30001741: + resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} chai@4.5.0: resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} engines: {node: '>=4'} - chalk@3.0.0: - resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} - engines: {node: '>=8'} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1521,22 +1768,21 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} - chromium-bidi@0.6.1: - resolution: {integrity: sha512-kSxJRj0VgtUKz6nmzc2JPfyfJGzwzt65u7PqhPHtgGQUZLF5oG+ST6l6e5ONfStUMAlhSutFCjaGKllXZa16jA==} - peerDependencies: - devtools-protocol: '*' + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} - chromium-bidi@3.0.0: - resolution: {integrity: sha512-ZOGRDAhBMX1uxL2Cm2TDuhImbrsEz5A/tTcVU6RpXEWaTNUNwsHW6njUXizh51Ir6iqHbKAfhA2XK33uBcLo5A==} + chromium-bidi@8.0.0: + resolution: {integrity: sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==} peerDependencies: devtools-protocol: '*' - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + ci-info@4.3.0: + resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} engines: {node: '>=8'} cliui@8.0.1: @@ -1550,13 +1796,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1571,12 +1810,16 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} @@ -1591,23 +1834,20 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - cssstyle@3.0.0: - resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==} - engines: {node: '>=14'} - - cssstyle@4.3.0: - resolution: {integrity: sha512-6r0NiY0xizYqfBvWp1G7WXJ06/bZyrk7Dc6PHql82C/pKGUTKu4yAX4Y8JPamb1ob9nBKuxWzCGTRuGwU3yxJQ==} + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - data-uri-to-buffer@2.0.2: - resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} - data-uri-to-buffer@6.0.2: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} @@ -1628,8 +1868,8 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -1637,13 +1877,17 @@ packages: supports-color: optional: true - decimal.js@10.5.0: - resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} deep-eql@4.1.4: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -1659,9 +1903,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -1674,24 +1915,17 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - devtools-protocol@0.0.1299070: - resolution: {integrity: sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==} - - devtools-protocol@0.0.1425554: - resolution: {integrity: sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw==} + devtools-protocol@0.0.1495869: + resolution: {integrity: sha512-i+bkd9UYFis40RcnkW7XrOprCujXRAHg62IVh/Ah3G8MmNXpCGt1m0dTFhSdx/AVs8XEMbdOGRwdkR1Bcta8AA==} diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -1713,8 +1947,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.138: - resolution: {integrity: sha512-FWlQc52z1dXqm+9cCJ2uyFgJkESd+16j6dBEjsgDNuHjBpuIzL8/lRc0uvh1k8RNI6waGo6tcy2DvwkTBJOLDg==} + electron-to-chromium@1.5.218: + resolution: {integrity: sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1722,11 +1956,11 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} env-paths@2.2.1: @@ -1736,8 +1970,11 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract@1.23.9: - resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -1755,6 +1992,9 @@ packages: resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -1771,13 +2011,18 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - esbuild@0.24.2: - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} hasBin: true @@ -1798,16 +2043,16 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-plugin-react-hooks@4.6.0: - resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + eslint-plugin-react-hooks@4.6.2: + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - eslint-plugin-react-refresh@0.4.4: - resolution: {integrity: sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==} + eslint-plugin-react-refresh@0.4.20: + resolution: {integrity: sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==} peerDependencies: - eslint: '>=7' + eslint: '>=8.40' eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} @@ -1823,8 +2068,8 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint@8.57.1: @@ -1864,24 +2109,17 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - exit-hook@2.2.1: - resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} - engines: {node: '>=6'} - - expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} - exsolve@1.0.5: - resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} + expect@30.1.2: + resolution: {integrity: sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} extract-zip@2.0.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} @@ -1913,6 +2151,15 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1925,6 +2172,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1940,13 +2190,18 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.2: - resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1981,17 +2236,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-source@2.0.12: - resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} - get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -2000,8 +2248,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-uri@6.0.4: - resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} engines: {node: '>= 14'} glob-parent@5.1.2: @@ -2012,9 +2260,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -2023,10 +2268,6 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -2035,10 +2276,6 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2091,10 +2328,6 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -2103,13 +2336,14 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -2137,8 +2371,8 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} is-arguments@1.2.0: @@ -2152,9 +2386,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -2163,10 +2394,6 @@ packages: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-boolean-object@1.2.2: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} @@ -2211,6 +2438,10 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + is-number-object@1.1.1: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} @@ -2238,10 +2469,6 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2288,8 +2515,8 @@ packages: resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} engines: {node: '>=10'} - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} iterator.prototype@1.1.5: @@ -2299,25 +2526,29 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-diff@30.1.2: + resolution: {integrity: sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@30.1.2: + resolution: {integrity: sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-message-util@30.1.0: + resolution: {integrity: sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-mock@30.0.5: + resolution: {integrity: sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-regex-util@30.0.1: + resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.0.5: + resolution: {integrity: sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} @@ -2333,11 +2564,8 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - - jsdom@23.0.1: - resolution: {integrity: sha512-2i27vgvlUsGEBO9+/kJQRbtqtm+191b5zAZrU/UezVmnC2dlDAFLgDYJvAEi94T4kjsRKkezEtLQTgsNEsW2lQ==} + jsdom@23.2.0: + resolution: {integrity: sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==} engines: {node: '>=18'} peerDependencies: canvas: ^2.11.2 @@ -2345,8 +2573,8 @@ packages: canvas: optional: true - jsdom@24.0.0: - resolution: {integrity: sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==} + jsdom@24.1.3: + resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==} engines: {node: '>=18'} peerDependencies: canvas: ^2.11.2 @@ -2383,6 +2611,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2412,9 +2644,6 @@ packages: lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2422,6 +2651,9 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2436,8 +2668,8 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -2450,6 +2682,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -2469,15 +2704,6 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} @@ -2486,9 +2712,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - miniflare@https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-miniflare-8586: - resolution: {tarball: https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-miniflare-8586} - version: 0.0.0-v1a1c5d714 + miniflare@4.20260312.0: + resolution: {integrity: sha512-pieP2rfXynPT6VRINYaiHe/tfMJ4c5OIhqRlIdLF6iZ9g5xgpEmvimvIgMpgAdDJuFlrLcwDUi8MfAo2R6dt/w==} engines: {node: '>=18.0.0'} hasBin: true @@ -2506,8 +2731,8 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mlly@1.7.4: - resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} @@ -2516,10 +2741,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -2535,23 +2756,15 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + node-releases@2.0.21: + resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - nwsapi@2.2.20: - resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} + nwsapi@2.2.22: + resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -2585,16 +2798,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -2638,8 +2844,8 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - parse5@7.2.1: - resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} @@ -2667,10 +2873,6 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -2680,12 +2882,16 @@ packages: pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + peerjs-js-binarypack@2.1.0: resolution: {integrity: sha512-YIwCC+pTzp3Bi8jPI9UFKO0t0SLo6xALnHkiNt/iUFmUUZG0fEEmEyFKvjsDKweiFitzHRyhuh6NvyJZ4nNxMg==} engines: {node: '>= 14.0.0'} - peerjs@1.5.4: - resolution: {integrity: sha512-yFsoLMnurJKlQbx6kVSBpOp+AlNldY1JQS2BrSsHLKCZnq6t7saHleuHM5svuLNbQkUJXHLF3sKOJB1K0xulOw==} + peerjs@1.5.5: + resolution: {integrity: sha512-viMUCPDL6CSfOu0ZqVcFqbWRXNHIbv2lPqNbrBIjbFYrflebOjItJ4hPfhjnuUCstqciHVu9vVJ7jFqqKi/EuQ==} engines: {node: '>= 14'} pend@1.2.0: @@ -2698,6 +2904,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -2705,8 +2915,18 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pnpm@9.5.0: - resolution: {integrity: sha512-FAA2gwEkYY1iSiGHtQ0EKJ1aCH8ybJ7fwMzXM9dsT1LDoxPU/BSHlKKp2BVTAWAE5nQujPhQZwJopzh/wiDJAw==} + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} + engines: {node: '>=18'} + hasBin: true + + pnpm@9.15.9: + resolution: {integrity: sha512-aARhQYk8ZvrQHAeSMRKOmvuJ74fiaR1p5NQO7iKJiClf1GghgbrlW1hBjDolO95lpQXsfF+UA+zlzDzTfc8lMQ==} engines: {node: '>=18.12'} hasBin: true @@ -2714,20 +2934,26 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} peerDependencies: + jiti: '>=1.21.0' postcss: '>=8.0.9' - ts-node: '>=9.0.0' + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: + jiti: + optional: true postcss: optional: true - ts-node: + tsx: + optional: true + yaml: optional: true - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2742,8 +2968,9 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - printable-characters@1.0.42: - resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} + pretty-format@30.0.5: + resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} @@ -2762,26 +2989,22 @@ packages: psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - pump@3.0.2: - resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - puppeteer-core@22.13.1: - resolution: {integrity: sha512-NmhnASYp51QPRCAf9n0OPxuPMmzkKd8+2sB9Q+BjwwCG25gz6iuNc3LQDWa+cH2tyivmJppLhNNFt6Q3HmoOpw==} - engines: {node: '>=18'} - - puppeteer-core@24.6.1: - resolution: {integrity: sha512-sMCxsY+OPWO2fecBrhIeCeJbWWXJ6UaN997sTid6whY0YT9XM0RnxEwLeUibluIS5/fRmuxe1efjb5RMBsky7g==} + puppeteer-core@24.20.0: + resolution: {integrity: sha512-n0y/f8EYyZt4yEJkjP3Vrqf9A4qa3uYpKYdsiedIY4bxIfTw1aAJSpSVPmWBPlr1LO4cNq2hGNIBWKPhvBF68w==} engines: {node: '>=18'} puppeteer-stream@3.0.20: resolution: {integrity: sha512-BdBL6VtXKrK9y9pYNV0btXdXJHdDM2Nyb1iUVhE1gs7xdtw2kUnux+k/PegDbHSN8CmPKhwpadXn6FVyqdhZoQ==} - puppeteer@22.13.1: - resolution: {integrity: sha512-PwXLDQK5u83Fm5A7TGMq+9BR7iHDJ8a3h21PSsh/E6VfhxiKYkU7+tvGZNSCap6k3pCNDd9oNteVBEctcBalmQ==} + puppeteer@24.20.0: + resolution: {integrity: sha512-iLnLV9oHKKAujmxiSxRWKfcT1q2COu0g1N9iU2TCp1MlmsyjgNAkcBOR3cAOqKb5UTiVPIGG4z5PO5yfpYZ6jA==} engines: {node: '>=18'} hasBin: true @@ -2791,10 +3014,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@18.2.0: - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: - react: ^18.2.0 + react: ^18.3.1 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -2805,17 +3028,17 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} @@ -2825,9 +3048,6 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -2836,6 +3056,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -2860,19 +3084,21 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rimraf@5.0.5: - resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} - engines: {node: '>=14'} + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true - rollup@4.40.0: - resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==} + rollup@4.50.1: + resolution: {integrity: sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true rrweb-cssom@0.6.0: resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} @@ -2901,15 +3127,20 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - sdp@3.2.0: - resolution: {integrity: sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==} + sdp@3.2.1: + resolution: {integrity: sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==} semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true @@ -2925,8 +3156,8 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: @@ -2956,16 +3187,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -2982,8 +3207,8 @@ packages: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} - socks@2.8.4: - resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} source-map-js@1.2.1: @@ -2997,9 +3222,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} - - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + deprecated: The work that was done in this beta branch won't be included in future versions stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} @@ -3008,9 +3231,6 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - stacktracey@2.1.8: - resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} - std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -3018,12 +3238,8 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - stoppable@1.1.0: - resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} - engines: {node: '>=4', npm: '>=6'} - - streamx@2.22.0: - resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} + streamx@2.22.1: + resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -3056,14 +3272,10 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -3084,6 +3296,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -3095,8 +3311,8 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tar-fs@3.0.8: - resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==} + tar-fs@3.1.0: + resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -3118,20 +3334,36 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + tinyspy@2.2.1: resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3167,8 +3399,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsup@8.0.2: - resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} + tsup@8.5.0: + resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -3186,38 +3418,38 @@ packages: typescript: optional: true - turbo-darwin-64@2.0.9: - resolution: {integrity: sha512-owlGsOaExuVGBUfrnJwjkL1BWlvefjSKczEAcpLx4BI7Oh6ttakOi+JyomkPkFlYElRpjbvlR2gP8WIn6M/+xQ==} + turbo-darwin-64@2.5.6: + resolution: {integrity: sha512-3C1xEdo4aFwMJAPvtlPqz1Sw/+cddWIOmsalHFMrsqqydcptwBfu26WW2cDm3u93bUzMbBJ8k3zNKFqxJ9ei2A==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.0.9: - resolution: {integrity: sha512-XAXkKkePth5ZPPE/9G9tTnPQx0C8UTkGWmNGYkpmGgRr8NedW+HrPsi9N0HcjzzIH9A4TpNYvtiV+WcwdaEjKA==} + turbo-darwin-arm64@2.5.6: + resolution: {integrity: sha512-LyiG+rD7JhMfYwLqB6k3LZQtYn8CQQUePbpA8mF/hMLPAekXdJo1g0bUPw8RZLwQXUIU/3BU7tXENvhSGz5DPA==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.0.9: - resolution: {integrity: sha512-l9wSgEjrCFM1aG16zItBsZ206ZlhSSx1owB8Cgskfv0XyIXRGHRkluihiaxkp+UeU5WoEfz4EN5toc+ICA0q0w==} + turbo-linux-64@2.5.6: + resolution: {integrity: sha512-GOcUTT0xiT/pSnHL4YD6Yr3HreUhU8pUcGqcI2ksIF9b2/r/kRHwGFcsHgpG3+vtZF/kwsP0MV8FTlTObxsYIA==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.0.9: - resolution: {integrity: sha512-gRnjxXRne18B27SwxXMqL3fJu7jw/8kBrOBTBNRSmZZiG1Uu3nbnP7b4lgrA/bCku6C0Wligwqurvtpq6+nFHA==} + turbo-linux-arm64@2.5.6: + resolution: {integrity: sha512-10Tm15bruJEA3m0V7iZcnQBpObGBcOgUcO+sY7/2vk1bweW34LMhkWi8svjV9iDF68+KJDThnYDlYE/bc7/zzQ==} cpu: [arm64] os: [linux] - turbo-windows-64@2.0.9: - resolution: {integrity: sha512-ZVo0apxUvaRq4Vm1qhsfqKKhtRgReYlBVf9MQvVU1O9AoyydEQvLDO1ryqpXDZWpcHoFxHAQc9msjAMtE5K2lA==} + turbo-windows-64@2.5.6: + resolution: {integrity: sha512-FyRsVpgaj76It0ludwZsNN40ytHN+17E4PFJyeliBEbxrGTc5BexlXVpufB7XlAaoaZVxbS6KT8RofLfDRyEPg==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.0.9: - resolution: {integrity: sha512-sGRz7c5Pey6y7y9OKi8ypbWNuIRPF9y8xcMqL56OZifSUSo+X2EOsOleR9MKxQXVaqHPGOUKWsE6y8hxBi9pag==} + turbo-windows-arm64@2.5.6: + resolution: {integrity: sha512-j/tWu8cMeQ7HPpKri6jvKtyXg9K1gRyhdK4tKrrchH8GNHscPX/F71zax58yYtLRWTiK04zNzPcUJuoS0+v/+Q==} cpu: [arm64] os: [win32] - turbo@2.0.9: - resolution: {integrity: sha512-QaLaUL1CqblSKKPgLrFW3lZWkWG4pGBQNW+q1ScJB5v1D/nFWtsrD/yZljW/bdawg90ihi4/ftQJ3h6fz1FamA==} + turbo@2.5.6: + resolution: {integrity: sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w==} hasBin: true type-check@0.4.0: @@ -3251,8 +3483,8 @@ packages: typed-query-selector@2.12.0: resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true @@ -3263,18 +3495,21 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - unbzip2-stream@1.4.3: - resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@5.29.0: - resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} - engines: {node: '>=14.0'} + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} - unenv@2.0.0-rc.15: - resolution: {integrity: sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA==} + undici@7.18.2: + resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} @@ -3292,16 +3527,18 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - urlpattern-polyfill@10.0.0: - resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} + vite-node@1.6.1: + resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true - vite-node@1.6.0: - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.0.0: - resolution: {integrity: sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw==} + vite@5.4.20: + resolution: {integrity: sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -3309,6 +3546,7 @@ packages: less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -3321,6 +3559,8 @@ packages: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -3328,15 +3568,40 @@ packages: terser: optional: true - vitest@1.6.0: - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + vitest@1.6.1: + resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 + '@vitest/browser': 1.6.1 + '@vitest/ui': 1.6.1 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -3357,6 +3622,9 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + webdriver-bidi-protocol@0.2.8: + resolution: {integrity: sha512-KPvtVAIX8VHjLZH1KHT5GXoOaPeb0Ju+JlAcdshw6Z/gsmRtLoxt0Hw99PgJwZta7zUQaAUIHHWDRkzrPHsQTQ==} + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -3364,8 +3632,8 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - webrtc-adapter@9.0.1: - resolution: {integrity: sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==} + webrtc-adapter@9.0.3: + resolution: {integrity: sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==} engines: {node: '>=6.0.0', npm: '>=3.10.0'} whatwg-encoding@3.1.1: @@ -3413,18 +3681,17 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - workerd@1.20250319.0: - resolution: {integrity: sha512-/+JfU0Iq+L2DWpJeBCvQIY88OkxoEV5IPbTpa+FFkBTW3eAyE7pV+DpvcvXfG94DFrA3D4z0EE9ZNwVKO5w42A==} + workerd@1.20260312.1: + resolution: {integrity: sha512-nNpPkw9jaqo79B+iBCOiksx+N62xC+ETIfyzofUEdY3cSOHJg6oNnVSHm7vHevzVblfV76c8Gr0cXHEapYMBEg==} engines: {node: '>=16'} hasBin: true - wrangler@https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-wrangler-8586: - resolution: {tarball: https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-wrangler-8586} - version: 0.0.0-v1a1c5d714 - engines: {node: '>=18.0.0'} + wrangler@4.73.0: + resolution: {integrity: sha512-VJXsqKDFCp6OtFEHXITSOR5kh95JOknwPY8m7RyQuWJQguSybJy43m4vhoCSt42prutTef7eeuw7L4V4xiynGw==} + engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20250319.0 + '@cloudflare/workers-types': ^4.20260312.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -3452,8 +3719,8 @@ packages: utf-8-validate: optional: true - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3502,353 +3769,432 @@ packages: resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} - youch@3.2.3: - resolution: {integrity: sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw==} - - zod@3.22.3: - resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - zod@3.24.3: - resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: - '@adobe/css-tools@4.4.2': {} + '@adobe/css-tools@4.4.4': {} '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - '@asamuzakjp/css-color@3.1.2': + '@asamuzakjp/css-color@3.2.0': dependencies: - '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 - '@babel/code-frame@7.26.2': + '@asamuzakjp/dom-selector@2.0.2': dependencies: - '@babel/helper-validator-identifier': 7.25.9 + bidi-js: 1.0.3 + css-tree: 2.3.1 + is-potential-custom-element-name: 1.0.1 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.26.8': {} + '@babel/compat-data@7.28.4': {} - '@babel/core@7.26.10': + '@babel/core@7.28.4': dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 - '@babel/helper-compilation-targets': 7.27.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) - '@babel/helpers': 7.27.0 - '@babel/parser': 7.27.0 - '@babel/template': 7.27.0 - '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.1 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/generator@7.27.0': + '@babel/generator@7.28.3': dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.0': + '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.26.8 - '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.4 + '@babel/compat-data': 7.28.4 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.26.0 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-module-imports@7.25.9': + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.10 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.27.0 + '@babel/core': 7.28.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.26.5': {} + '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-identifier@7.27.1': {} - '@babel/helper-validator-option@7.25.9': {} + '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.27.0': + '@babel/helpers@7.28.4': dependencies: - '@babel/template': 7.27.0 - '@babel/types': 7.27.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 - '@babel/parser@7.27.0': + '@babel/parser@7.28.4': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.28.4 - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 - '@babel/runtime@7.27.0': - dependencies: - regenerator-runtime: 0.14.1 + '@babel/runtime@7.28.4': {} - '@babel/template@7.27.0': + '@babel/template@7.27.2': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 - '@babel/traverse@7.27.0': + '@babel/traverse@7.28.4': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 - '@babel/parser': 7.27.0 - '@babel/template': 7.27.0 - '@babel/types': 7.27.0 - debug: 4.4.0 - globals: 11.12.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + debug: 4.4.1 transitivePeerDependencies: - supports-color - '@babel/types@7.27.0': + '@babel/types@7.28.4': dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 '@bcoe/v8-coverage@0.2.3': {} - '@cloudflare/kv-asset-handler@https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-cloudflare-kv-asset-handler-8586': - dependencies: - mime: 3.0.0 + '@cloudflare/kv-asset-handler@0.4.2': {} - '@cloudflare/unenv-preset@https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-cloudflare-unenv-preset-8586(unenv@2.0.0-rc.15)(workerd@1.20250319.0)': + '@cloudflare/unenv-preset@2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260312.1)': dependencies: - unenv: 2.0.0-rc.15 + unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20250319.0 + workerd: 1.20260312.1 - '@cloudflare/workerd-darwin-64@1.20250319.0': + '@cloudflare/workerd-darwin-64@1.20260312.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20250319.0': + '@cloudflare/workerd-darwin-arm64@1.20260312.1': optional: true - '@cloudflare/workerd-linux-64@1.20250319.0': + '@cloudflare/workerd-linux-64@1.20260312.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20250319.0': + '@cloudflare/workerd-linux-arm64@1.20260312.1': optional: true - '@cloudflare/workerd-windows-64@1.20250319.0': - optional: true - - '@cloudflare/workers-types@4.20250319.0': + '@cloudflare/workerd-windows-64@1.20260312.1': optional: true '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@5.0.2': {} + '@csstools/color-helpers@5.1.0': {} - '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-color-parser@3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@csstools/color-helpers': 5.0.2 - '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': dependencies: - '@csstools/css-tokenizer': 3.0.3 + '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-tokenizer@3.0.3': {} + '@csstools/css-tokenizer@3.0.4': {} - '@emnapi/runtime@1.4.3': + '@emnapi/runtime@1.9.0': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.19.12': + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.25.9': + optional: true + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.25.9': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.25.9': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.25.9': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.25.9': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.25.9': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.9': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.25.9': optional: true - '@esbuild/aix-ppc64@0.24.2': + '@esbuild/freebsd-x64@0.27.3': optional: true - '@esbuild/android-arm64@0.19.12': + '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.24.2': + '@esbuild/linux-arm64@0.25.9': optional: true - '@esbuild/android-arm@0.19.12': + '@esbuild/linux-arm64@0.27.3': optional: true - '@esbuild/android-arm@0.24.2': + '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/android-x64@0.19.12': + '@esbuild/linux-arm@0.25.9': optional: true - '@esbuild/android-x64@0.24.2': + '@esbuild/linux-arm@0.27.3': optional: true - '@esbuild/darwin-arm64@0.19.12': + '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/darwin-arm64@0.24.2': + '@esbuild/linux-ia32@0.25.9': optional: true - '@esbuild/darwin-x64@0.19.12': + '@esbuild/linux-ia32@0.27.3': optional: true - '@esbuild/darwin-x64@0.24.2': + '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.19.12': + '@esbuild/linux-loong64@0.25.9': optional: true - '@esbuild/freebsd-arm64@0.24.2': + '@esbuild/linux-loong64@0.27.3': optional: true - '@esbuild/freebsd-x64@0.19.12': + '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/freebsd-x64@0.24.2': + '@esbuild/linux-mips64el@0.25.9': optional: true - '@esbuild/linux-arm64@0.19.12': + '@esbuild/linux-mips64el@0.27.3': optional: true - '@esbuild/linux-arm64@0.24.2': + '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-arm@0.19.12': + '@esbuild/linux-ppc64@0.25.9': optional: true - '@esbuild/linux-arm@0.24.2': + '@esbuild/linux-ppc64@0.27.3': optional: true - '@esbuild/linux-ia32@0.19.12': + '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-ia32@0.24.2': + '@esbuild/linux-riscv64@0.25.9': optional: true - '@esbuild/linux-loong64@0.19.12': + '@esbuild/linux-riscv64@0.27.3': optional: true - '@esbuild/linux-loong64@0.24.2': + '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-mips64el@0.19.12': + '@esbuild/linux-s390x@0.25.9': optional: true - '@esbuild/linux-mips64el@0.24.2': + '@esbuild/linux-s390x@0.27.3': optional: true - '@esbuild/linux-ppc64@0.19.12': + '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.24.2': + '@esbuild/linux-x64@0.25.9': optional: true - '@esbuild/linux-riscv64@0.19.12': + '@esbuild/linux-x64@0.27.3': optional: true - '@esbuild/linux-riscv64@0.24.2': + '@esbuild/netbsd-arm64@0.25.9': optional: true - '@esbuild/linux-s390x@0.19.12': + '@esbuild/netbsd-arm64@0.27.3': optional: true - '@esbuild/linux-s390x@0.24.2': + '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.19.12': + '@esbuild/netbsd-x64@0.25.9': optional: true - '@esbuild/linux-x64@0.24.2': + '@esbuild/netbsd-x64@0.27.3': optional: true - '@esbuild/netbsd-arm64@0.24.2': + '@esbuild/openbsd-arm64@0.25.9': optional: true - '@esbuild/netbsd-x64@0.19.12': + '@esbuild/openbsd-arm64@0.27.3': optional: true - '@esbuild/netbsd-x64@0.24.2': + '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-arm64@0.24.2': + '@esbuild/openbsd-x64@0.25.9': optional: true - '@esbuild/openbsd-x64@0.19.12': + '@esbuild/openbsd-x64@0.27.3': optional: true - '@esbuild/openbsd-x64@0.24.2': + '@esbuild/openharmony-arm64@0.25.9': optional: true - '@esbuild/sunos-x64@0.19.12': + '@esbuild/openharmony-arm64@0.27.3': optional: true - '@esbuild/sunos-x64@0.24.2': + '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/win32-arm64@0.19.12': + '@esbuild/sunos-x64@0.25.9': optional: true - '@esbuild/win32-arm64@0.24.2': + '@esbuild/sunos-x64@0.27.3': optional: true - '@esbuild/win32-ia32@0.19.12': + '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-ia32@0.24.2': + '@esbuild/win32-arm64@0.25.9': optional: true - '@esbuild/win32-x64@0.19.12': + '@esbuild/win32-arm64@0.27.3': optional: true - '@esbuild/win32-x64@0.24.2': + '@esbuild/win32-ia32@0.21.5': optional: true - '@eslint-community/eslint-utils@4.6.1(eslint@8.57.1)': + '@esbuild/win32-ia32@0.25.9': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.25.9': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@8.57.1)': dependencies: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 @@ -3858,7 +4204,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.0 + debug: 4.4.1 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -3871,12 +4217,10 @@ snapshots: '@eslint/js@8.57.1': {} - '@fastify/busboy@2.1.1': {} - '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -3885,130 +4229,167 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} - '@img/sharp-darwin-arm64@0.33.5': + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-arm64': 1.2.4 optional: true - '@img/sharp-darwin-x64@0.33.5': + '@img/sharp-darwin-x64@0.34.5': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': + '@img/sharp-libvips-linux-ppc64@1.2.4': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': + '@img/sharp-libvips-linux-riscv64@1.2.4': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': + '@img/sharp-libvips-linux-s390x@1.2.4': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': + '@img/sharp-libvips-linux-x64@1.2.4': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': + '@img/sharp-libvips-linuxmusl-x64@1.2.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 optional: true - '@img/sharp-linux-arm64@0.33.5': + '@img/sharp-linux-ppc64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 optional: true - '@img/sharp-linux-arm@0.33.5': + '@img/sharp-linux-riscv64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-riscv64': 1.2.4 optional: true - '@img/sharp-linux-s390x@0.33.5': + '@img/sharp-linux-s390x@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 optional: true - '@img/sharp-linux-x64@0.33.5': + '@img/sharp-linux-x64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.2.4 optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': + '@img/sharp-linuxmusl-arm64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 optional: true - '@img/sharp-linuxmusl-x64@0.33.5': + '@img/sharp-linuxmusl-x64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 optional: true - '@img/sharp-wasm32@0.33.5': + '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.4.3 + '@emnapi/runtime': 1.9.0 optional: true - '@img/sharp-win32-ia32@0.33.5': + '@img/sharp-win32-arm64@0.34.5': optional: true - '@img/sharp-win32-x64@0.33.5': + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': optional: true '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 '@istanbuljs/schema@0.1.3': {} - '@jest/expect-utils@29.7.0': + '@jest/diff-sequences@30.0.1': {} + + '@jest/expect-utils@30.1.2': dependencies: - jest-get-type: 29.6.3 + '@jest/get-type': 30.1.0 + + '@jest/get-type@30.1.0': {} + + '@jest/pattern@30.0.1': + dependencies: + '@types/node': 24.3.1 + jest-regex-util: 30.0.1 '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 - '@jest/types@29.6.3': + '@jest/schemas@30.0.5': dependencies: - '@jest/schemas': 29.6.3 + '@sinclair/typebox': 0.34.41 + + '@jest/types@30.0.5': + dependencies: + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.0.0 + '@types/node': 24.3.1 '@types/yargs': 17.0.33 chalk: 4.1.2 - '@jridgewell/gen-mapping@0.3.8': + '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/set-array@1.2.1': {} + '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@msgpack/msgpack@2.8.0': {} @@ -4027,101 +4408,115 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.58.2': + dependencies: + playwright: 1.58.2 + '@polka/url@1.0.0-next.29': {} - '@puppeteer/browsers@2.10.0': + '@poppinss/colors@4.1.6': dependencies: - debug: 4.4.0 - extract-zip: 2.0.1 - progress: 2.0.3 - proxy-agent: 6.5.0 - semver: 7.7.1 - tar-fs: 3.0.8 - yargs: 17.7.2 - transitivePeerDependencies: - - bare-buffer - - supports-color + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 - '@puppeteer/browsers@2.2.4': + '@poppinss/exception@1.2.3': {} + + '@puppeteer/browsers@2.10.9': dependencies: - debug: 4.4.0 + debug: 4.4.1 extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.5.0 - semver: 7.7.1 - tar-fs: 3.0.8 - unbzip2-stream: 1.4.3 + semver: 7.7.2 + tar-fs: 3.1.0 yargs: 17.7.2 transitivePeerDependencies: - bare-buffer + - react-native-b4a - supports-color - '@rollup/rollup-android-arm-eabi@4.40.0': + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.50.1': + optional: true + + '@rollup/rollup-android-arm64@4.50.1': optional: true - '@rollup/rollup-android-arm64@4.40.0': + '@rollup/rollup-darwin-arm64@4.50.1': optional: true - '@rollup/rollup-darwin-arm64@4.40.0': + '@rollup/rollup-darwin-x64@4.50.1': optional: true - '@rollup/rollup-darwin-x64@4.40.0': + '@rollup/rollup-freebsd-arm64@4.50.1': optional: true - '@rollup/rollup-freebsd-arm64@4.40.0': + '@rollup/rollup-freebsd-x64@4.50.1': optional: true - '@rollup/rollup-freebsd-x64@4.40.0': + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.40.0': + '@rollup/rollup-linux-arm-musleabihf@4.50.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.40.0': + '@rollup/rollup-linux-arm64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.40.0': + '@rollup/rollup-linux-arm64-musl@4.50.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.40.0': + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.40.0': + '@rollup/rollup-linux-ppc64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': + '@rollup/rollup-linux-riscv64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.40.0': + '@rollup/rollup-linux-riscv64-musl@4.50.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.40.0': + '@rollup/rollup-linux-s390x-gnu@4.50.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.40.0': + '@rollup/rollup-linux-x64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.40.0': + '@rollup/rollup-linux-x64-musl@4.50.1': optional: true - '@rollup/rollup-linux-x64-musl@4.40.0': + '@rollup/rollup-openharmony-arm64@4.50.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.40.0': + '@rollup/rollup-win32-arm64-msvc@4.50.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.40.0': + '@rollup/rollup-win32-ia32-msvc@4.50.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.40.0': + '@rollup/rollup-win32-x64-msvc@4.50.1': optional: true '@sinclair/typebox@0.27.8': {} + '@sinclair/typebox@0.34.41': {} + + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.14': {} + '@testing-library/dom@10.4.0': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/runtime': 7.27.0 + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.4 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -4131,60 +4526,43 @@ snapshots: '@testing-library/dom@9.3.4': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/runtime': 7.27.0 + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.4 '@types/aria-query': 5.0.4 aria-query: 5.1.3 chalk: 4.1.2 dom-accessibility-api: 0.5.16 lz-string: 1.5.0 - pretty-format: 27.5.1 - - '@testing-library/jest-dom@6.4.2(@types/jest@29.5.14)(vitest@1.6.0(@types/node@22.14.1)(jsdom@23.0.1))': - dependencies: - '@adobe/css-tools': 4.4.2 - '@babel/runtime': 7.27.0 - aria-query: 5.3.2 - chalk: 3.0.0 - css.escape: 1.5.1 - dom-accessibility-api: 0.6.3 - lodash: 4.17.21 - redent: 3.0.0 - optionalDependencies: - '@types/jest': 29.5.14 - vitest: 1.6.0(@types/node@22.14.1)(jsdom@23.0.1) + pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.2(@types/jest@29.5.14)(vitest@1.6.0(@types/node@22.14.1)(jsdom@24.0.0))': + '@testing-library/jest-dom@6.8.0': dependencies: - '@adobe/css-tools': 4.4.2 - '@babel/runtime': 7.27.0 + '@adobe/css-tools': 4.4.4 aria-query: 5.3.2 - chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 - lodash: 4.17.21 + picocolors: 1.1.1 redent: 3.0.0 - optionalDependencies: - '@types/jest': 29.5.14 - vitest: 1.6.0(@types/node@22.14.1)(jsdom@24.0.0) - '@testing-library/react@14.2.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@testing-library/react@14.3.1(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.28.4 '@testing-library/dom': 9.3.4 - '@types/react-dom': 18.2.15 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@types/react-dom': 18.3.7(@types/react@18.3.24) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' - '@testing-library/react@16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.28.4 '@testing-library/dom': 10.4.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.2.37 - '@types/react-dom': 18.2.15 + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': dependencies: @@ -4196,30 +4574,32 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.7 + '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.28.4 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 - '@types/babel__traverse@7.20.7': + '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.28.4 - '@types/bun@1.2.10': + '@types/bun@1.2.21(@types/react@18.3.24)': dependencies: - bun-types: 1.2.10 + bun-types: 1.2.21(@types/react@18.3.24) + transitivePeerDependencies: + - '@types/react' - '@types/estree@1.0.7': {} + '@types/estree@1.0.8': {} '@types/istanbul-lib-coverage@2.0.6': {} @@ -4231,46 +4611,51 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 - '@types/jest@29.5.14': + '@types/jest@30.0.0': dependencies: - expect: 29.7.0 - pretty-format: 29.7.0 + expect: 30.1.2 + pretty-format: 30.0.5 - '@types/node@18.11.18': {} + '@types/node@18.19.124': + dependencies: + undici-types: 5.26.5 - '@types/node@20.0.0': {} + '@types/node@20.19.13': + dependencies: + undici-types: 6.21.0 - '@types/node@22.14.1': + '@types/node@22.18.1': dependencies: undici-types: 6.21.0 + '@types/node@24.3.1': + dependencies: + undici-types: 7.10.0 + '@types/peerjs@1.1.0': dependencies: - peerjs: 1.5.4 + peerjs: 1.5.5 - '@types/prop-types@15.7.14': {} + '@types/prop-types@15.7.15': {} - '@types/react-dom@18.2.15': + '@types/react-dom@18.3.7(@types/react@18.3.24)': dependencies: - '@types/react': 18.2.37 + '@types/react': 18.3.24 - '@types/react@18.2.37': + '@types/react@18.3.24': dependencies: - '@types/prop-types': 15.7.14 - '@types/scheduler': 0.26.0 + '@types/prop-types': 15.7.15 csstype: 3.1.3 - '@types/scheduler@0.26.0': {} - '@types/stack-utils@2.0.3': {} - '@types/testing-library__jest-dom@5.14.5': + '@types/testing-library__jest-dom@5.14.9': dependencies: - '@types/jest': 29.5.14 + '@types/jest': 30.0.0 - '@types/ws@8.5.11': + '@types/ws@8.18.1': dependencies: - '@types/node': 20.0.0 + '@types/node': 18.19.124 '@types/yargs-parser@21.0.3': {} @@ -4280,172 +4665,226 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.0.0 + '@types/node': 22.18.1 optional: true - '@typescript-eslint/eslint-plugin@8.30.1(@typescript-eslint/parser@8.30.1(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1)(typescript@5.4.5)': + '@typescript-eslint/eslint-plugin@8.43.0(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.30.1(eslint@8.57.1)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 8.30.1 - '@typescript-eslint/type-utils': 8.30.1(eslint@8.57.1)(typescript@5.4.5) - '@typescript-eslint/utils': 8.30.1(eslint@8.57.1)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 8.30.1 + '@typescript-eslint/parser': 8.43.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.43.0 + '@typescript-eslint/type-utils': 8.43.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/utils': 8.43.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.43.0 eslint: 8.57.1 graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.4.5) - typescript: 5.4.5 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.30.1(eslint@8.57.1)(typescript@5.4.5)': + '@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/scope-manager': 8.30.1 - '@typescript-eslint/types': 8.30.1 - '@typescript-eslint/typescript-estree': 8.30.1(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 8.30.1 - debug: 4.4.0 + '@typescript-eslint/scope-manager': 8.43.0 + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.43.0 + debug: 4.4.1 eslint: 8.57.1 - typescript: 5.4.5 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.43.0(typescript@5.9.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2) + '@typescript-eslint/types': 8.43.0 + debug: 4.4.1 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.30.1': + '@typescript-eslint/scope-manager@8.43.0': + dependencies: + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/visitor-keys': 8.43.0 + + '@typescript-eslint/tsconfig-utils@8.43.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.30.1 - '@typescript-eslint/visitor-keys': 8.30.1 + typescript: 5.9.2 - '@typescript-eslint/type-utils@8.30.1(eslint@8.57.1)(typescript@5.4.5)': + '@typescript-eslint/type-utils@8.43.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.30.1(typescript@5.4.5) - '@typescript-eslint/utils': 8.30.1(eslint@8.57.1)(typescript@5.4.5) - debug: 4.4.0 + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.43.0(eslint@8.57.1)(typescript@5.9.2) + debug: 4.4.1 eslint: 8.57.1 - ts-api-utils: 2.1.0(typescript@5.4.5) - typescript: 5.4.5 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.30.1': {} + '@typescript-eslint/types@8.43.0': {} - '@typescript-eslint/typescript-estree@8.30.1(typescript@5.4.5)': + '@typescript-eslint/typescript-estree@8.43.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.30.1 - '@typescript-eslint/visitor-keys': 8.30.1 - debug: 4.4.0 + '@typescript-eslint/project-service': 8.43.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2) + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/visitor-keys': 8.43.0 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.1.0(typescript@5.4.5) - typescript: 5.4.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.30.1(eslint@8.57.1)(typescript@5.4.5)': + '@typescript-eslint/utils@8.43.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.6.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.30.1 - '@typescript-eslint/types': 8.30.1 - '@typescript-eslint/typescript-estree': 8.30.1(typescript@5.4.5) + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.43.0 + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) eslint: 8.57.1 - typescript: 5.4.5 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.30.1': + '@typescript-eslint/visitor-keys@8.43.0': dependencies: - '@typescript-eslint/types': 8.30.1 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.43.0 + eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@4.2.0(vite@5.0.0(@types/node@22.14.1))': + '@vitejs/plugin-react@4.7.0(vite@5.4.20(@types/node@24.3.1))': dependencies: - '@babel/core': 7.26.10 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) + '@babel/core': 7.28.4 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) + '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.0.0(@types/node@22.14.1) + react-refresh: 0.17.0 + vite: 5.4.20(@types/node@24.3.1) transitivePeerDependencies: - supports-color - '@vitest/browser@1.6.1(vitest@1.6.0)': + '@vitest/browser@1.6.1(playwright@1.58.2)(vitest@1.6.1)': + dependencies: + '@vitest/utils': 1.6.1 + magic-string: 0.30.19 + sirv: 2.0.4 + vitest: 1.6.1(@types/node@20.19.13)(@vitest/browser@1.6.1)(jsdom@24.1.3) + optionalDependencies: + playwright: 1.58.2 + + '@vitest/browser@1.6.1(vitest@1.6.1)': dependencies: '@vitest/utils': 1.6.1 - magic-string: 0.30.17 + magic-string: 0.30.19 sirv: 2.0.4 - vitest: 1.6.0(@types/node@20.0.0)(@vitest/browser@1.6.1)(jsdom@24.0.0) + vitest: 1.6.1(@types/node@24.3.1)(@vitest/browser@1.6.1)(jsdom@24.1.3) + optional: true - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@18.11.18)(jsdom@24.0.0))': + '@vitest/coverage-v8@1.6.1(vitest@1.6.1(@types/node@18.19.124)(@vitest/browser@1.6.1)(jsdom@24.1.3))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 - debug: 4.4.0 + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - magic-string: 0.30.17 + istanbul-reports: 3.2.0 + magic-string: 0.30.19 magicast: 0.3.5 picocolors: 1.1.1 std-env: 3.9.0 strip-literal: 2.1.1 test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@18.11.18)(jsdom@24.0.0) + vitest: 1.6.1(@types/node@18.19.124)(@vitest/browser@1.6.1)(jsdom@24.1.3) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@22.14.1)(jsdom@24.0.0))': + '@vitest/coverage-v8@1.6.1(vitest@1.6.1(@types/node@24.3.1)(@vitest/browser@1.6.1)(jsdom@24.1.3))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 - debug: 4.4.0 + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - magic-string: 0.30.17 + istanbul-reports: 3.2.0 + magic-string: 0.30.19 magicast: 0.3.5 picocolors: 1.1.1 std-env: 3.9.0 strip-literal: 2.1.1 test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@22.14.1)(jsdom@24.0.0) + vitest: 1.6.1(@types/node@24.3.1)(@vitest/browser@1.6.1)(jsdom@24.1.3) transitivePeerDependencies: - supports-color - '@vitest/expect@1.6.0': + '@vitest/expect@1.6.1': dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 chai: 4.5.0 - '@vitest/runner@1.6.0': + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.20(@types/node@22.18.1))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.19 + optionalDependencies: + vite: 5.4.20(@types/node@22.18.1) + + '@vitest/pretty-format@2.1.9': dependencies: - '@vitest/utils': 1.6.0 + tinyrainbow: 1.2.0 + + '@vitest/runner@1.6.1': + dependencies: + '@vitest/utils': 1.6.1 p-limit: 5.0.0 pathe: 1.1.2 - '@vitest/snapshot@1.6.0': + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@1.6.1': dependencies: - magic-string: 0.30.17 + magic-string: 0.30.19 pathe: 1.1.2 pretty-format: 29.7.0 - '@vitest/spy@1.6.0': + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.19 + pathe: 1.1.2 + + '@vitest/spy@1.6.1': dependencies: tinyspy: 2.2.1 - '@vitest/utils@1.6.0': + '@vitest/spy@2.1.9': dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + tinyspy: 3.0.2 '@vitest/utils@1.6.1': dependencies: @@ -4454,21 +4893,23 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 - acorn-jsx@5.3.2(acorn@8.14.1): + '@vitest/utils@2.1.9': dependencies: - acorn: 8.14.1 + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 - acorn-walk@8.3.2: {} + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 acorn-walk@8.3.4: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 - acorn@8.14.0: {} + acorn@8.15.0: {} - acorn@8.14.1: {} - - agent-base@7.1.3: {} + agent-base@7.1.4: {} ajv@6.12.6: dependencies: @@ -4479,7 +4920,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.1.0: {} + ansi-regex@6.2.2: {} ansi-styles@4.3.0: dependencies: @@ -4487,15 +4928,10 @@ snapshots: ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} any-promise@1.3.0: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - argparse@2.0.1: {} aria-query@5.1.3: @@ -4513,22 +4949,22 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 - array-includes@3.1.8: + array-includes@3.1.9: dependencies: call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 is-string: 1.1.1 - - array-union@2.1.0: {} + math-intrinsics: 1.1.0 array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 @@ -4537,21 +4973,21 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 array.prototype.tosorted@1.1.4: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-shim-unscopables: 1.1.0 @@ -4560,17 +4996,15 @@ snapshots: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 - as-table@1.0.55: - dependencies: - printable-characters: 1.0.42 - assertion-error@1.1.0: {} + assertion-error@2.0.1: {} + ast-types@0.13.4: dependencies: tslib: 2.8.1 @@ -4583,49 +5017,62 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - b4a@1.6.7: {} + b4a@1.7.1: {} balanced-match@1.0.2: {} - bare-events@2.5.4: + bare-events@2.6.1: optional: true - bare-fs@4.1.2: + bare-fs@4.4.4: dependencies: - bare-events: 2.5.4 + bare-events: 2.6.1 bare-path: 3.0.0 - bare-stream: 2.6.5(bare-events@2.5.4) + bare-stream: 2.7.0(bare-events@2.6.1) + bare-url: 2.2.2 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - react-native-b4a optional: true - bare-os@3.6.1: + bare-os@3.6.2: optional: true bare-path@3.0.0: dependencies: - bare-os: 3.6.1 + bare-os: 3.6.2 optional: true - bare-stream@2.6.5(bare-events@2.5.4): + bare-stream@2.7.0(bare-events@2.6.1): dependencies: - streamx: 2.22.0 + streamx: 2.22.1 optionalDependencies: - bare-events: 2.5.4 + bare-events: 2.6.1 + transitivePeerDependencies: + - react-native-b4a + optional: true + + bare-url@2.2.2: + dependencies: + bare-path: 3.0.0 optional: true - base64-js@1.5.1: {} + baseline-browser-mapping@2.8.2: {} basic-ftp@5.0.5: {} - binary-extensions@2.3.0: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 blake3-wasm@2.1.5: {} - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -4633,27 +5080,24 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.4: + browserslist@4.26.0: dependencies: - caniuse-lite: 1.0.30001714 - electron-to-chromium: 1.5.138 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.24.4) + baseline-browser-mapping: 2.8.2 + caniuse-lite: 1.0.30001741 + electron-to-chromium: 1.5.218 + node-releases: 2.0.21 + update-browserslist-db: 1.1.3(browserslist@4.26.0) buffer-crc32@0.2.13: {} - buffer@5.7.1: + bun-types@1.2.21(@types/react@18.3.24): dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 + '@types/node': 22.18.1 + '@types/react': 18.3.24 - bun-types@1.2.10: + bundle-require@5.1.0(esbuild@0.25.9): dependencies: - '@types/node': 22.14.1 - - bundle-require@4.2.1(esbuild@0.19.12): - dependencies: - esbuild: 0.19.12 + esbuild: 0.25.9 load-tsconfig: 0.2.5 cac@6.7.14: {} @@ -4677,7 +5121,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001714: {} + caniuse-lite@1.0.30001741: {} chai@4.5.0: dependencies: @@ -4689,10 +5133,13 @@ snapshots: pathval: 1.1.1 type-detect: 4.1.0 - chalk@3.0.0: + chai@5.3.3: dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 chalk@4.1.2: dependencies: @@ -4703,32 +5150,19 @@ snapshots: dependencies: get-func-name: 2.0.2 - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 + check-error@2.1.1: {} - chromium-bidi@0.6.1(devtools-protocol@0.0.1299070): + chokidar@4.0.3: dependencies: - devtools-protocol: 0.0.1299070 - mitt: 3.0.1 - urlpattern-polyfill: 10.0.0 - zod: 3.23.8 + readdirp: 4.1.2 - chromium-bidi@3.0.0(devtools-protocol@0.0.1425554): + chromium-bidi@8.0.0(devtools-protocol@0.0.1495869): dependencies: - devtools-protocol: 0.0.1425554 + devtools-protocol: 0.0.1495869 mitt: 3.0.1 - zod: 3.24.3 + zod: 3.25.76 - ci-info@3.9.0: {} + ci-info@4.3.0: {} cliui@8.0.1: dependencies: @@ -4742,18 +5176,6 @@ snapshots: color-name@1.1.4: {} - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.2 - optional: true - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - optional: true - combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -4764,18 +5186,20 @@ snapshots: confbox@0.1.8: {} + consola@3.4.2: {} + convert-source-map@2.0.0: {} - cookie@0.5.0: {} + cookie@1.1.1: {} - cosmiconfig@9.0.0(typescript@5.4.5): + cosmiconfig@9.0.0(typescript@5.9.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.4.5 + typescript: 5.9.2 cross-spawn@7.0.6: dependencies: @@ -4783,21 +5207,20 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css.escape@1.5.1: {} - - cssstyle@3.0.0: + css-tree@2.3.1: dependencies: - rrweb-cssom: 0.6.0 + mdn-data: 2.0.30 + source-map-js: 1.2.1 - cssstyle@4.3.0: + css.escape@1.5.1: {} + + cssstyle@4.6.0: dependencies: - '@asamuzakjp/css-color': 3.1.2 + '@asamuzakjp/css-color': 3.2.0 rrweb-cssom: 0.8.0 csstype@3.1.3: {} - data-uri-to-buffer@2.0.2: {} - data-uri-to-buffer@6.0.2: {} data-urls@5.0.0: @@ -4823,16 +5246,18 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - debug@4.4.0: + debug@4.4.1: dependencies: ms: 2.1.3 - decimal.js@10.5.0: {} + decimal.js@10.6.0: {} deep-eql@4.1.4: dependencies: type-detect: 4.1.0 + deep-eql@5.0.2: {} + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.2 @@ -4868,8 +5293,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - defu@6.1.4: {} - degenerator@5.0.1: dependencies: ast-types: 0.13.4 @@ -4880,19 +5303,12 @@ snapshots: dequal@2.0.3: {} - detect-libc@2.0.3: - optional: true - - devtools-protocol@0.0.1299070: {} + detect-libc@2.1.2: {} - devtools-protocol@0.0.1425554: {} + devtools-protocol@0.0.1495869: {} diff-sequences@29.6.3: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -4913,17 +5329,17 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.138: {} + electron-to-chromium@1.5.218: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} - end-of-stream@1.4.4: + end-of-stream@1.4.5: dependencies: once: 1.4.0 - entities@4.5.0: {} + entities@6.0.1: {} env-paths@2.2.1: {} @@ -4931,7 +5347,9 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-abstract@1.23.9: + error-stack-parser-es@1.0.5: {} + + es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 @@ -4960,7 +5378,9 @@ snapshots: is-array-buffer: 3.0.5 is-callable: 1.2.7 is-data-view: 1.0.2 + is-negative-zero: 2.0.3 is-regex: 1.2.1 + is-set: 2.0.3 is-shared-array-buffer: 1.0.4 is-string: 1.1.1 is-typed-array: 1.1.15 @@ -4975,6 +5395,7 @@ snapshots: safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 string.prototype.trim: 1.2.10 string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 @@ -5006,7 +5427,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 @@ -5020,6 +5441,8 @@ snapshots: iterator.prototype: 1.1.5 safe-array-concat: 1.1.3 + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -5041,59 +5464,89 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.19.12: + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.25.9: optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 - - esbuild@0.24.2: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + + esbuild@0.27.3: optionalDependencies: - '@esbuild/aix-ppc64': 0.24.2 - '@esbuild/android-arm': 0.24.2 - '@esbuild/android-arm64': 0.24.2 - '@esbuild/android-x64': 0.24.2 - '@esbuild/darwin-arm64': 0.24.2 - '@esbuild/darwin-x64': 0.24.2 - '@esbuild/freebsd-arm64': 0.24.2 - '@esbuild/freebsd-x64': 0.24.2 - '@esbuild/linux-arm': 0.24.2 - '@esbuild/linux-arm64': 0.24.2 - '@esbuild/linux-ia32': 0.24.2 - '@esbuild/linux-loong64': 0.24.2 - '@esbuild/linux-mips64el': 0.24.2 - '@esbuild/linux-ppc64': 0.24.2 - '@esbuild/linux-riscv64': 0.24.2 - '@esbuild/linux-s390x': 0.24.2 - '@esbuild/linux-x64': 0.24.2 - '@esbuild/netbsd-arm64': 0.24.2 - '@esbuild/netbsd-x64': 0.24.2 - '@esbuild/openbsd-arm64': 0.24.2 - '@esbuild/openbsd-x64': 0.24.2 - '@esbuild/sunos-x64': 0.24.2 - '@esbuild/win32-arm64': 0.24.2 - '@esbuild/win32-ia32': 0.24.2 - '@esbuild/win32-x64': 0.24.2 + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 escalade@3.2.0: {} @@ -5109,17 +5562,17 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-plugin-react-hooks@4.6.0(eslint@8.57.1): + eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): dependencies: eslint: 8.57.1 - eslint-plugin-react-refresh@0.4.4(eslint@8.57.1): + eslint-plugin-react-refresh@0.4.20(eslint@8.57.1): dependencies: eslint: 8.57.1 eslint-plugin-react@7.37.5(eslint@8.57.1): dependencies: - array-includes: 3.1.8 + array-includes: 3.1.9 array.prototype.findlast: 1.2.5 array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 @@ -5146,11 +5599,11 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.6.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) '@eslint-community/regexpp': 4.12.1 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 @@ -5161,7 +5614,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.1 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -5193,8 +5646,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -5211,24 +5664,12 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 esutils@2.0.3: {} eventemitter3@4.0.7: {} - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -5241,21 +5682,20 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - exit-hook@2.2.1: {} + expect-type@1.2.2: {} - expect@29.7.0: + expect@30.1.2: dependencies: - '@jest/expect-utils': 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - - exsolve@1.0.5: {} + '@jest/expect-utils': 30.1.2 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 + jest-mock: 30.0.5 + jest-util: 30.0.5 extract-zip@2.0.1: dependencies: - debug: 4.4.0 + debug: 4.4.1 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -5289,6 +5729,10 @@ snapshots: dependencies: pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -5302,6 +5746,12 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.19 + mlly: 1.8.0 + rollup: 4.50.1 + flat-cache@3.2.0: dependencies: flatted: 3.3.3 @@ -5319,15 +5769,19 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.2: + form-data@4.0.4: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 + hasown: 2.0.2 mime-types: 2.1.35 fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -5368,16 +5822,9 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-source@2.0.12: - dependencies: - data-uri-to-buffer: 2.0.2 - source-map: 0.6.1 - get-stream@5.2.0: dependencies: - pump: 3.0.2 - - get-stream@6.0.1: {} + pump: 3.0.3 get-stream@8.0.1: {} @@ -5387,11 +5834,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-uri@6.0.4: + get-uri@6.0.5: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -5403,8 +5850,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob-to-regexp@0.4.1: {} - glob@10.4.5: dependencies: foreground-child: 3.3.1 @@ -5423,8 +5868,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - globals@11.12.0: {} - globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -5434,15 +5877,6 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -5479,30 +5913,28 @@ snapshots: http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.3 - debug: 4.4.0 + agent-base: 7.1.4 + debug: 4.4.1 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: - agent-base: 7.1.3 - debug: 4.4.0 + agent-base: 7.1.4 + debug: 4.4.1 transitivePeerDependencies: - supports-color - human-signals@2.1.0: {} - human-signals@5.0.0: {} iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} - ignore@5.3.2: {} + ignore@7.0.5: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -5527,10 +5959,7 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - ip-address@9.0.5: - dependencies: - jsbn: 1.1.0 - sprintf-js: 1.1.3 + ip-address@10.0.1: {} is-arguments@1.2.0: dependencies: @@ -5545,9 +5974,6 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.2: - optional: true - is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -5560,10 +5986,6 @@ snapshots: dependencies: has-bigints: 1.1.0 - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - is-boolean-object@1.2.2: dependencies: call-bound: 1.0.4 @@ -5607,6 +6029,8 @@ snapshots: is-map@2.0.3: {} + is-negative-zero@2.0.3: {} + is-number-object@1.1.1: dependencies: call-bound: 1.0.4 @@ -5631,8 +6055,6 @@ snapshots: dependencies: call-bound: 1.0.4 - is-stream@2.0.1: {} - is-stream@3.0.0: {} is-string@1.1.1: @@ -5675,13 +6097,13 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.0 + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color - istanbul-reports@3.1.7: + istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 @@ -5701,42 +6123,48 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jest-diff@29.7.0: + jest-diff@30.1.2: dependencies: + '@jest/diff-sequences': 30.0.1 + '@jest/get-type': 30.1.0 chalk: 4.1.2 - diff-sequences: 29.6.3 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 + pretty-format: 30.0.5 - jest-get-type@29.6.3: {} - - jest-matcher-utils@29.7.0: + jest-matcher-utils@30.1.2: dependencies: + '@jest/get-type': 30.1.0 chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 + jest-diff: 30.1.2 + pretty-format: 30.0.5 - jest-message-util@29.7.0: + jest-message-util@30.1.0: dependencies: - '@babel/code-frame': 7.26.2 - '@jest/types': 29.6.3 + '@babel/code-frame': 7.27.1 + '@jest/types': 30.0.5 '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 micromatch: 4.0.8 - pretty-format: 29.7.0 + pretty-format: 30.0.5 slash: 3.0.0 stack-utils: 2.0.6 - jest-util@29.7.0: + jest-mock@30.0.5: + dependencies: + '@jest/types': 30.0.5 + '@types/node': 24.3.1 + jest-util: 30.0.5 + + jest-regex-util@30.0.1: {} + + jest-util@30.0.5: dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.0.0 + '@jest/types': 30.0.5 + '@types/node': 24.3.1 chalk: 4.1.2 - ci-info: 3.9.0 + ci-info: 4.3.0 graceful-fs: 4.2.11 - picomatch: 2.3.1 + picomatch: 4.0.3 joycon@3.1.1: {} @@ -5748,20 +6176,18 @@ snapshots: dependencies: argparse: 2.0.1 - jsbn@1.1.0: {} - - jsdom@23.0.1: + jsdom@23.2.0: dependencies: - cssstyle: 3.0.0 + '@asamuzakjp/dom-selector': 2.0.2 + cssstyle: 4.6.0 data-urls: 5.0.0 - decimal.js: 10.5.0 - form-data: 4.0.2 + decimal.js: 10.6.0 + form-data: 4.0.4 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.20 - parse5: 7.2.1 + parse5: 7.3.0 rrweb-cssom: 0.6.0 saxes: 6.0.0 symbol-tree: 3.2.4 @@ -5771,26 +6197,26 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.18.1 + ws: 8.18.3 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - jsdom@24.0.0: + jsdom@24.1.3: dependencies: - cssstyle: 4.3.0 + cssstyle: 4.6.0 data-urls: 5.0.0 - decimal.js: 10.5.0 - form-data: 4.0.2 + decimal.js: 10.6.0 + form-data: 4.0.4 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.20 - parse5: 7.2.1 - rrweb-cssom: 0.6.0 + nwsapi: 2.2.22 + parse5: 7.3.0 + rrweb-cssom: 0.7.1 saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 4.1.4 @@ -5799,7 +6225,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.18.1 + ws: 8.18.3 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -5820,7 +6246,7 @@ snapshots: jsx-ast-utils@3.3.5: dependencies: - array-includes: 3.1.8 + array-includes: 3.1.9 array.prototype.flat: 1.3.3 object.assign: 4.1.7 object.values: 1.2.1 @@ -5829,6 +6255,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kleur@4.1.5: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -5842,7 +6270,7 @@ snapshots: local-pkg@0.5.1: dependencies: - mlly: 1.7.4 + mlly: 1.8.0 pkg-types: 1.3.1 locate-path@6.0.0: @@ -5853,8 +6281,6 @@ snapshots: lodash.sortby@4.7.0: {} - lodash@4.17.21: {} - loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -5863,6 +6289,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + loupe@3.2.1: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -5873,22 +6301,24 @@ snapshots: lz-string@1.5.0: {} - magic-string@0.30.17: + magic-string@0.30.19: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 magicast@0.3.5: dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 source-map-js: 1.2.1 make-dir@4.0.0: dependencies: - semver: 7.7.1 + semver: 7.7.2 math-intrinsics@1.1.0: {} + mdn-data@2.0.30: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -5904,46 +6334,37 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@3.0.0: {} - - mimic-fn@2.1.0: {} - mimic-fn@4.0.0: {} min-indent@1.0.1: {} - miniflare@https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-miniflare-8586: + miniflare@4.20260312.0: dependencies: '@cspotcode/source-map-support': 0.8.1 - acorn: 8.14.0 - acorn-walk: 8.3.2 - exit-hook: 2.2.1 - glob-to-regexp: 0.4.1 - stoppable: 1.1.0 - undici: 5.29.0 - workerd: 1.20250319.0 + sharp: 0.34.5 + undici: 7.18.2 + workerd: 1.20260312.1 ws: 8.18.0 - youch: 3.2.3 - zod: 3.22.3 + youch: 4.1.0-beta.10 transitivePeerDependencies: - bufferutil - utf-8-validate minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minipass@7.1.2: {} mitt@3.0.1: {} - mlly@1.7.4: + mlly@1.8.0: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.6.1 @@ -5952,8 +6373,6 @@ snapshots: ms@2.1.3: {} - mustache@4.2.0: {} - mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -5966,19 +6385,13 @@ snapshots: netmask@2.0.2: {} - node-releases@2.0.19: {} - - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 + node-releases@2.0.21: {} npm-run-path@5.3.0: dependencies: path-key: 4.0.0 - nwsapi@2.2.20: {} + nwsapi@2.2.22: {} object-assign@4.1.1: {} @@ -6011,7 +6424,7 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-object-atoms: 1.1.1 object.values@1.2.1: @@ -6021,16 +6434,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - ohash@2.0.11: {} - once@1.4.0: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - onetime@6.0.0: dependencies: mimic-fn: 4.0.0 @@ -6065,9 +6472,9 @@ snapshots: pac-proxy-agent@7.2.0: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.3 - debug: 4.4.0 - get-uri: 6.0.4 + agent-base: 7.1.4 + debug: 4.4.1 + get-uri: 6.0.5 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 pac-resolver: 7.0.1 @@ -6088,14 +6495,14 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.26.2 + '@babel/code-frame': 7.27.1 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse5@7.2.1: + parse5@7.3.0: dependencies: - entities: 4.5.0 + entities: 6.0.1 path-exists@4.0.0: {} @@ -6114,22 +6521,22 @@ snapshots: path-to-regexp@6.3.0: {} - path-type@4.0.0: {} - pathe@1.1.2: {} pathe@2.0.3: {} pathval@1.1.1: {} + pathval@2.0.1: {} + peerjs-js-binarypack@2.1.0: {} - peerjs@1.5.4: + peerjs@1.5.5: dependencies: '@msgpack/msgpack': 2.8.0 eventemitter3: 4.0.7 peerjs-js-binarypack: 2.1.0 - webrtc-adapter: 9.0.1 + webrtc-adapter: 9.0.3 pend@1.2.0: {} @@ -6137,26 +6544,36 @@ snapshots: picomatch@2.3.1: {} + picomatch@4.0.3: {} + pirates@4.0.7: {} pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.7.4 + mlly: 1.8.0 pathe: 2.0.3 - pnpm@9.5.0: {} + playwright-core@1.58.2: {} + + playwright@1.58.2: + dependencies: + playwright-core: 1.58.2 + optionalDependencies: + fsevents: 2.3.2 + + pnpm@9.15.9: {} possible-typed-array-names@1.1.0: {} - postcss-load-config@4.0.2(postcss@8.5.3): + postcss-load-config@6.0.1(postcss@8.5.6)(yaml@2.7.1): dependencies: lilconfig: 3.1.3 - yaml: 2.7.1 optionalDependencies: - postcss: 8.5.3 + postcss: 8.5.6 + yaml: 2.7.1 - postcss@8.5.3: + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -6176,7 +6593,11 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - printable-characters@1.0.42: {} + pretty-format@30.0.5: + dependencies: + '@jest/schemas': 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 progress@2.0.3: {} @@ -6188,8 +6609,8 @@ snapshots: proxy-agent@6.5.0: dependencies: - agent-base: 7.1.3 - debug: 4.4.0 + agent-base: 7.1.4 + debug: 4.4.1 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -6205,59 +6626,52 @@ snapshots: dependencies: punycode: 2.3.1 - pump@3.0.2: + pump@3.0.3: dependencies: - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 once: 1.4.0 punycode@2.3.1: {} - puppeteer-core@22.13.1: - dependencies: - '@puppeteer/browsers': 2.2.4 - chromium-bidi: 0.6.1(devtools-protocol@0.0.1299070) - debug: 4.4.0 - devtools-protocol: 0.0.1299070 - ws: 8.18.1 - transitivePeerDependencies: - - bare-buffer - - bufferutil - - supports-color - - utf-8-validate - - puppeteer-core@24.6.1: + puppeteer-core@24.20.0: dependencies: - '@puppeteer/browsers': 2.10.0 - chromium-bidi: 3.0.0(devtools-protocol@0.0.1425554) - debug: 4.4.0 - devtools-protocol: 0.0.1425554 + '@puppeteer/browsers': 2.10.9 + chromium-bidi: 8.0.0(devtools-protocol@0.0.1495869) + debug: 4.4.1 + devtools-protocol: 0.0.1495869 typed-query-selector: 2.12.0 - ws: 8.18.1 + webdriver-bidi-protocol: 0.2.8 + ws: 8.18.3 transitivePeerDependencies: - bare-buffer - bufferutil + - react-native-b4a - supports-color - utf-8-validate puppeteer-stream@3.0.20: dependencies: - puppeteer-core: 24.6.1 - ws: 8.18.1 + puppeteer-core: 24.20.0 + ws: 8.18.3 transitivePeerDependencies: - bare-buffer - bufferutil + - react-native-b4a - supports-color - utf-8-validate - puppeteer@22.13.1(typescript@5.4.5): + puppeteer@24.20.0(typescript@5.9.2): dependencies: - '@puppeteer/browsers': 2.2.4 - cosmiconfig: 9.0.0(typescript@5.4.5) - devtools-protocol: 0.0.1299070 - puppeteer-core: 22.13.1 + '@puppeteer/browsers': 2.10.9 + chromium-bidi: 8.0.0(devtools-protocol@0.0.1495869) + cosmiconfig: 9.0.0(typescript@5.9.2) + devtools-protocol: 0.0.1495869 + puppeteer-core: 24.20.0 + typed-query-selector: 2.12.0 transitivePeerDependencies: - bare-buffer - bufferutil + - react-native-b4a - supports-color - typescript - utf-8-validate @@ -6266,10 +6680,10 @@ snapshots: queue-microtask@1.2.3: {} - react-dom@18.2.0(react@18.2.0): + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 - react: 18.2.0 + react: 18.3.1 scheduler: 0.23.2 react-is@16.13.1: {} @@ -6278,15 +6692,13 @@ snapshots: react-is@18.3.1: {} - react-refresh@0.14.2: {} + react-refresh@0.17.0: {} - react@18.2.0: + react@18.3.1: dependencies: loose-envify: 1.4.0 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 + readdirp@4.1.2: {} redent@3.0.0: dependencies: @@ -6297,15 +6709,13 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 - regenerator-runtime@0.14.1: {} - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -6317,6 +6727,8 @@ snapshots: require-directory@2.1.1: {} + require-from-string@2.0.2: {} + requires-port@1.0.0: {} resolve-from@4.0.0: {} @@ -6335,38 +6747,41 @@ snapshots: dependencies: glob: 7.2.3 - rimraf@5.0.5: + rimraf@5.0.10: dependencies: glob: 10.4.5 - rollup@4.40.0: + rollup@4.50.1: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.40.0 - '@rollup/rollup-android-arm64': 4.40.0 - '@rollup/rollup-darwin-arm64': 4.40.0 - '@rollup/rollup-darwin-x64': 4.40.0 - '@rollup/rollup-freebsd-arm64': 4.40.0 - '@rollup/rollup-freebsd-x64': 4.40.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.40.0 - '@rollup/rollup-linux-arm-musleabihf': 4.40.0 - '@rollup/rollup-linux-arm64-gnu': 4.40.0 - '@rollup/rollup-linux-arm64-musl': 4.40.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.40.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.40.0 - '@rollup/rollup-linux-riscv64-gnu': 4.40.0 - '@rollup/rollup-linux-riscv64-musl': 4.40.0 - '@rollup/rollup-linux-s390x-gnu': 4.40.0 - '@rollup/rollup-linux-x64-gnu': 4.40.0 - '@rollup/rollup-linux-x64-musl': 4.40.0 - '@rollup/rollup-win32-arm64-msvc': 4.40.0 - '@rollup/rollup-win32-ia32-msvc': 4.40.0 - '@rollup/rollup-win32-x64-msvc': 4.40.0 + '@rollup/rollup-android-arm-eabi': 4.50.1 + '@rollup/rollup-android-arm64': 4.50.1 + '@rollup/rollup-darwin-arm64': 4.50.1 + '@rollup/rollup-darwin-x64': 4.50.1 + '@rollup/rollup-freebsd-arm64': 4.50.1 + '@rollup/rollup-freebsd-x64': 4.50.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.50.1 + '@rollup/rollup-linux-arm-musleabihf': 4.50.1 + '@rollup/rollup-linux-arm64-gnu': 4.50.1 + '@rollup/rollup-linux-arm64-musl': 4.50.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.50.1 + '@rollup/rollup-linux-ppc64-gnu': 4.50.1 + '@rollup/rollup-linux-riscv64-gnu': 4.50.1 + '@rollup/rollup-linux-riscv64-musl': 4.50.1 + '@rollup/rollup-linux-s390x-gnu': 4.50.1 + '@rollup/rollup-linux-x64-gnu': 4.50.1 + '@rollup/rollup-linux-x64-musl': 4.50.1 + '@rollup/rollup-openharmony-arm64': 4.50.1 + '@rollup/rollup-win32-arm64-msvc': 4.50.1 + '@rollup/rollup-win32-ia32-msvc': 4.50.1 + '@rollup/rollup-win32-x64-msvc': 4.50.1 fsevents: 2.3.3 rrweb-cssom@0.6.0: {} + rrweb-cssom@0.7.1: {} + rrweb-cssom@0.8.0: {} run-parallel@1.2.0: @@ -6402,11 +6817,13 @@ snapshots: dependencies: loose-envify: 1.4.0 - sdp@3.2.0: {} + sdp@3.2.1: {} semver@6.3.1: {} - semver@7.7.1: {} + semver@7.7.2: {} + + semver@7.7.4: {} set-function-length@1.2.2: dependencies: @@ -6430,32 +6847,36 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - sharp@0.33.5: + sharp@0.34.5: dependencies: - color: 4.2.3 - detect-libc: 2.0.3 - semver: 7.7.1 + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 - optional: true + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 shebang-command@2.0.0: dependencies: @@ -6493,15 +6914,8 @@ snapshots: siginfo@2.0.0: {} - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} - simple-swizzle@0.2.2: - dependencies: - is-arrayish: 0.3.2 - optional: true - sirv@2.0.4: dependencies: '@polka/url': 1.0.0-next.29 @@ -6514,38 +6928,32 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: - agent-base: 7.1.3 - debug: 4.4.0 - socks: 2.8.4 + agent-base: 7.1.4 + debug: 4.4.1 + socks: 2.8.7 transitivePeerDependencies: - supports-color - socks@2.8.4: + socks@2.8.7: dependencies: - ip-address: 9.0.5 + ip-address: 10.0.1 smart-buffer: 4.2.0 source-map-js@1.2.1: {} - source-map@0.6.1: {} + source-map@0.6.1: + optional: true source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 - sprintf-js@1.1.3: {} - stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 stackback@0.0.2: {} - stacktracey@2.1.8: - dependencies: - as-table: 1.0.55 - get-source: 2.0.12 - std-env@3.9.0: {} stop-iteration-iterator@1.1.0: @@ -6553,14 +6961,14 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - stoppable@1.1.0: {} - - streamx@2.22.0: + streamx@2.22.1: dependencies: fast-fifo: 1.3.2 text-decoder: 1.2.3 optionalDependencies: - bare-events: 2.5.4 + bare-events: 2.6.1 + transitivePeerDependencies: + - react-native-b4a string-width@4.2.3: dependencies: @@ -6572,14 +6980,14 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -6593,7 +7001,7 @@ snapshots: string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 string.prototype.trim@1.2.10: dependencies: @@ -6601,7 +7009,7 @@ snapshots: call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 @@ -6622,11 +7030,9 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.1.2: dependencies: - ansi-regex: 6.1.0 - - strip-final-newline@2.0.0: {} + ansi-regex: 6.2.2 strip-final-newline@3.0.0: {} @@ -6642,7 +7048,7 @@ snapshots: sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/gen-mapping': 0.3.13 commander: 4.1.1 glob: 10.4.5 lines-and-columns: 1.2.4 @@ -6650,6 +7056,8 @@ snapshots: pirates: 4.0.7 ts-interface-checker: 0.1.13 + supports-color@10.2.2: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -6658,21 +7066,24 @@ snapshots: symbol-tree@3.2.4: {} - tar-fs@3.0.8: + tar-fs@3.1.0: dependencies: - pump: 3.0.2 + pump: 3.0.3 tar-stream: 3.1.7 optionalDependencies: - bare-fs: 4.1.2 + bare-fs: 4.4.4 bare-path: 3.0.0 transitivePeerDependencies: - bare-buffer + - react-native-b4a tar-stream@3.1.7: dependencies: - b4a: 1.6.7 + b4a: 1.7.1 fast-fifo: 1.3.2 - streamx: 2.22.0 + streamx: 2.22.1 + transitivePeerDependencies: + - react-native-b4a test-exclude@6.0.0: dependencies: @@ -6682,7 +7093,9 @@ snapshots: text-decoder@1.2.3: dependencies: - b4a: 1.6.7 + b4a: 1.7.1 + transitivePeerDependencies: + - react-native-b4a text-table@0.2.0: {} @@ -6694,14 +7107,25 @@ snapshots: dependencies: any-promise: 1.3.0 - through@2.3.8: {} - tinybench@2.9.0: {} + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinypool@0.8.4: {} + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + tinyspy@2.2.1: {} + tinyspy@3.0.2: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -6725,63 +7149,68 @@ snapshots: tree-kill@1.2.2: {} - ts-api-utils@2.1.0(typescript@5.4.5): + ts-api-utils@2.1.0(typescript@5.9.2): dependencies: - typescript: 5.4.5 + typescript: 5.9.2 ts-interface-checker@0.1.13: {} tslib@2.8.1: {} - tsup@8.0.2(postcss@8.5.3)(typescript@5.4.5): + tsup@8.5.0(postcss@8.5.6)(typescript@5.9.2)(yaml@2.7.1): dependencies: - bundle-require: 4.2.1(esbuild@0.19.12) + bundle-require: 5.1.0(esbuild@0.25.9) cac: 6.7.14 - chokidar: 3.6.0 - debug: 4.4.0 - esbuild: 0.19.12 - execa: 5.1.1 - globby: 11.1.0 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.1 + esbuild: 0.25.9 + fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.5.3) + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.6)(yaml@2.7.1) resolve-from: 5.0.0 - rollup: 4.40.0 + rollup: 4.50.1 source-map: 0.8.0-beta.0 sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.5.3 - typescript: 5.4.5 + postcss: 8.5.6 + typescript: 5.9.2 transitivePeerDependencies: + - jiti - supports-color - - ts-node + - tsx + - yaml - turbo-darwin-64@2.0.9: + turbo-darwin-64@2.5.6: optional: true - turbo-darwin-arm64@2.0.9: + turbo-darwin-arm64@2.5.6: optional: true - turbo-linux-64@2.0.9: + turbo-linux-64@2.5.6: optional: true - turbo-linux-arm64@2.0.9: + turbo-linux-arm64@2.5.6: optional: true - turbo-windows-64@2.0.9: + turbo-windows-64@2.5.6: optional: true - turbo-windows-arm64@2.0.9: + turbo-windows-arm64@2.5.6: optional: true - turbo@2.0.9: + turbo@2.5.6: optionalDependencies: - turbo-darwin-64: 2.0.9 - turbo-darwin-arm64: 2.0.9 - turbo-linux-64: 2.0.9 - turbo-linux-arm64: 2.0.9 - turbo-windows-64: 2.0.9 - turbo-windows-arm64: 2.0.9 + turbo-darwin-64: 2.5.6 + turbo-darwin-arm64: 2.5.6 + turbo-linux-64: 2.5.6 + turbo-linux-arm64: 2.5.6 + turbo-windows-64: 2.5.6 + turbo-windows-arm64: 2.5.6 type-check@0.4.0: dependencies: @@ -6826,7 +7255,7 @@ snapshots: typed-query-selector@2.12.0: {} - typescript@5.4.5: {} + typescript@5.9.2: {} ufo@1.6.1: {} @@ -6837,30 +7266,23 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - unbzip2-stream@1.4.3: - dependencies: - buffer: 5.7.1 - through: 2.3.8 + undici-types@5.26.5: {} undici-types@6.21.0: {} - undici@5.29.0: - dependencies: - '@fastify/busboy': 2.1.1 + undici-types@7.10.0: {} + + undici@7.18.2: {} - unenv@2.0.0-rc.15: + unenv@2.0.0-rc.24: dependencies: - defu: 6.1.4 - exsolve: 1.0.5 - ohash: 2.0.11 pathe: 2.0.3 - ufo: 1.6.1 universalify@0.2.0: {} - update-browserslist-db@1.1.3(browserslist@4.24.4): + update-browserslist-db@1.1.3(browserslist@4.26.0): dependencies: - browserslist: 4.24.4 + browserslist: 4.26.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -6873,218 +7295,289 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - urlpattern-polyfill@10.0.0: {} - - vite-node@1.6.0(@types/node@18.11.18): + vite-node@1.6.1(@types/node@18.19.124): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.1 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.0.0(@types/node@18.11.18) + vite: 5.4.20(@types/node@18.19.124) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vite-node@1.6.0(@types/node@20.0.0): + vite-node@1.6.1(@types/node@20.19.13): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.1 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.0.0(@types/node@20.0.0) + vite: 5.4.20(@types/node@20.19.13) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vite-node@1.6.0(@types/node@22.14.1): + vite-node@1.6.1(@types/node@24.3.1): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.1 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.0.0(@types/node@22.14.1) + vite: 5.4.20(@types/node@24.3.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite-node@2.1.9(@types/node@22.18.1): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.20(@types/node@22.18.1) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vite@5.0.0(@types/node@18.11.18): + vite@5.4.20(@types/node@18.19.124): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.50.1 + optionalDependencies: + '@types/node': 18.19.124 + fsevents: 2.3.3 + + vite@5.4.20(@types/node@20.19.13): dependencies: - esbuild: 0.19.12 - postcss: 8.5.3 - rollup: 4.40.0 + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.50.1 optionalDependencies: - '@types/node': 18.11.18 + '@types/node': 20.19.13 fsevents: 2.3.3 - vite@5.0.0(@types/node@20.0.0): + vite@5.4.20(@types/node@22.18.1): dependencies: - esbuild: 0.19.12 - postcss: 8.5.3 - rollup: 4.40.0 + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.50.1 optionalDependencies: - '@types/node': 20.0.0 + '@types/node': 22.18.1 fsevents: 2.3.3 - vite@5.0.0(@types/node@22.14.1): + vite@5.4.20(@types/node@24.3.1): dependencies: - esbuild: 0.19.12 - postcss: 8.5.3 - rollup: 4.40.0 + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.50.1 optionalDependencies: - '@types/node': 22.14.1 + '@types/node': 24.3.1 fsevents: 2.3.3 - vitest@1.6.0(@types/node@18.11.18)(jsdom@24.0.0): + vitest@1.6.1(@types/node@18.19.124)(@vitest/browser@1.6.1)(jsdom@24.1.3): dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.4.0 + debug: 4.4.1 execa: 8.0.1 local-pkg: 0.5.1 - magic-string: 0.30.17 + magic-string: 0.30.19 pathe: 1.1.2 picocolors: 1.1.1 std-env: 3.9.0 strip-literal: 2.1.1 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.0.0(@types/node@18.11.18) - vite-node: 1.6.0(@types/node@18.11.18) + vite: 5.4.20(@types/node@18.19.124) + vite-node: 1.6.1(@types/node@18.19.124) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 18.11.18 - jsdom: 24.0.0 + '@types/node': 18.19.124 + '@vitest/browser': 1.6.1(vitest@1.6.1) + jsdom: 24.1.3 transitivePeerDependencies: - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vitest@1.6.0(@types/node@20.0.0)(@vitest/browser@1.6.1)(jsdom@24.0.0): + vitest@1.6.1(@types/node@20.19.13)(@vitest/browser@1.6.1)(jsdom@24.1.3): dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.4.0 + debug: 4.4.1 execa: 8.0.1 local-pkg: 0.5.1 - magic-string: 0.30.17 + magic-string: 0.30.19 pathe: 1.1.2 picocolors: 1.1.1 std-env: 3.9.0 strip-literal: 2.1.1 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.0.0(@types/node@20.0.0) - vite-node: 1.6.0(@types/node@20.0.0) + vite: 5.4.20(@types/node@20.19.13) + vite-node: 1.6.1(@types/node@20.19.13) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.0.0 - '@vitest/browser': 1.6.1(vitest@1.6.0) - jsdom: 24.0.0 + '@types/node': 20.19.13 + '@vitest/browser': 1.6.1(playwright@1.58.2)(vitest@1.6.1) + jsdom: 24.1.3 transitivePeerDependencies: - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vitest@1.6.0(@types/node@22.14.1)(jsdom@23.0.1): + vitest@1.6.1(@types/node@24.3.1)(@vitest/browser@1.6.1)(jsdom@23.2.0): dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.4.0 + debug: 4.4.1 execa: 8.0.1 local-pkg: 0.5.1 - magic-string: 0.30.17 + magic-string: 0.30.19 pathe: 1.1.2 picocolors: 1.1.1 std-env: 3.9.0 strip-literal: 2.1.1 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.0.0(@types/node@22.14.1) - vite-node: 1.6.0(@types/node@22.14.1) + vite: 5.4.20(@types/node@24.3.1) + vite-node: 1.6.1(@types/node@24.3.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.14.1 - jsdom: 23.0.1 + '@types/node': 24.3.1 + '@vitest/browser': 1.6.1(vitest@1.6.1) + jsdom: 23.2.0 transitivePeerDependencies: - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vitest@1.6.0(@types/node@22.14.1)(jsdom@24.0.0): + vitest@1.6.1(@types/node@24.3.1)(@vitest/browser@1.6.1)(jsdom@24.1.3): dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.4.0 + debug: 4.4.1 execa: 8.0.1 local-pkg: 0.5.1 - magic-string: 0.30.17 + magic-string: 0.30.19 pathe: 1.1.2 picocolors: 1.1.1 std-env: 3.9.0 strip-literal: 2.1.1 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.0.0(@types/node@22.14.1) - vite-node: 1.6.0(@types/node@22.14.1) + vite: 5.4.20(@types/node@24.3.1) + vite-node: 1.6.1(@types/node@24.3.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.3.1 + '@vitest/browser': 1.6.1(vitest@1.6.1) + jsdom: 24.1.3 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vitest@2.1.9(@types/node@22.18.1)(jsdom@24.1.3): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.20(@types/node@22.18.1)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.1 + expect-type: 1.2.2 + magic-string: 0.30.19 + pathe: 1.1.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.20(@types/node@22.18.1) + vite-node: 2.1.9(@types/node@22.18.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.14.1 - jsdom: 24.0.0 + '@types/node': 22.18.1 + jsdom: 24.1.3 transitivePeerDependencies: - less - lightningcss + - msw - sass + - sass-embedded - stylus - sugarss - supports-color @@ -7094,13 +7587,15 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + webdriver-bidi-protocol@0.2.8: {} + webidl-conversions@4.0.2: {} webidl-conversions@7.0.0: {} - webrtc-adapter@9.0.1: + webrtc-adapter@9.0.3: dependencies: - sdp: 3.2.0 + sdp: 3.2.1 whatwg-encoding@3.1.1: dependencies: @@ -7171,28 +7666,26 @@ snapshots: word-wrap@1.2.5: {} - workerd@1.20250319.0: + workerd@1.20260312.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20250319.0 - '@cloudflare/workerd-darwin-arm64': 1.20250319.0 - '@cloudflare/workerd-linux-64': 1.20250319.0 - '@cloudflare/workerd-linux-arm64': 1.20250319.0 - '@cloudflare/workerd-windows-64': 1.20250319.0 + '@cloudflare/workerd-darwin-64': 1.20260312.1 + '@cloudflare/workerd-darwin-arm64': 1.20260312.1 + '@cloudflare/workerd-linux-64': 1.20260312.1 + '@cloudflare/workerd-linux-arm64': 1.20260312.1 + '@cloudflare/workerd-windows-64': 1.20260312.1 - wrangler@https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-wrangler-8586(@cloudflare/workers-types@4.20250319.0): + wrangler@4.73.0: dependencies: - '@cloudflare/kv-asset-handler': https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-cloudflare-kv-asset-handler-8586 - '@cloudflare/unenv-preset': https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-cloudflare-unenv-preset-8586(unenv@2.0.0-rc.15)(workerd@1.20250319.0) + '@cloudflare/kv-asset-handler': 0.4.2 + '@cloudflare/unenv-preset': 2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260312.1) blake3-wasm: 2.1.5 - esbuild: 0.24.2 - miniflare: https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/13973948516/npm-package-miniflare-8586 + esbuild: 0.27.3 + miniflare: 4.20260312.0 path-to-regexp: 6.3.0 - unenv: 2.0.0-rc.15 - workerd: 1.20250319.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260312.1 optionalDependencies: - '@cloudflare/workers-types': 4.20250319.0 fsevents: 2.3.3 - sharp: 0.33.5 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -7205,15 +7698,15 @@ snapshots: wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 wrappy@1.0.2: {} ws@8.18.0: {} - ws@8.18.1: {} + ws@8.18.3: {} xml-name-validator@5.0.0: {} @@ -7223,7 +7716,8 @@ snapshots: yallist@3.1.1: {} - yaml@2.7.1: {} + yaml@2.7.1: + optional: true yargs-parser@21.1.1: {} @@ -7246,14 +7740,17 @@ snapshots: yocto-queue@1.2.1: {} - youch@3.2.3: + youch-core@0.3.3: dependencies: - cookie: 0.5.0 - mustache: 4.2.0 - stacktracey: 2.1.8 + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 - zod@3.22.3: {} - - zod@3.23.8: {} + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.14 + cookie: 1.1.1 + youch-core: 0.3.3 - zod@3.24.3: {} + zod@3.25.76: {}