diff --git a/README.md b/README.md index 73505a8d..688a7a7f 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Chat](https://img.shields.io/matrix/webrtc:matrix.org)](https://matrix.to/#/#webrtc:matrix.org) [![Localazy](https://img.shields.io/endpoint?url=https%3A%2F%2Fconnect.localazy.com%2Fstatus%2Felement-call%2Fdata%3Fcontent%3Dall%26title%3Dlocalazy%26logo%3Dtrue)](https://localazy.com/p/element-call) [![License](https://img.shields.io/github/license/element-hq/element-call)](LICENSE-AGPL-3.0) +[![Codecov](https://img.shields.io/codecov/c/github/element-hq/element-call)](https://app.codecov.io/gh/element-hq/element-call) [🎬 Live Demo 🎬](https://call.element.io) diff --git a/backend/dev_homeserver-othersite.yaml b/backend/dev_homeserver-othersite.yaml index 947e33cd..81e775ca 100644 --- a/backend/dev_homeserver-othersite.yaml +++ b/backend/dev_homeserver-othersite.yaml @@ -38,6 +38,8 @@ experimental_features: # MSC4222 needed for syncv2 state_after. This allow clients to # correctly track the state of the room. msc4222_enabled: true + # sticky events for MatrixRTC user state + msc4354_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no diff --git a/backend/dev_homeserver.yaml b/backend/dev_homeserver.yaml index fe89d95a..dc7b42c8 100644 --- a/backend/dev_homeserver.yaml +++ b/backend/dev_homeserver.yaml @@ -38,7 +38,7 @@ experimental_features: # MSC4222 needed for syncv2 state_after. This allow clients to # correctly track the state of the room. msc4222_enabled: true - # sticky events for matrixRTC user state + # sticky events for MatrixRTC user state msc4354_enabled: true # The maximum allowed duration by which sent events can be delayed, as diff --git a/backend/playwright_homeserver-othersite.yaml b/backend/playwright_homeserver-othersite.yaml index 5cb0dd65..35640ae9 100644 --- a/backend/playwright_homeserver-othersite.yaml +++ b/backend/playwright_homeserver-othersite.yaml @@ -38,6 +38,8 @@ experimental_features: # MSC4222 needed for syncv2 state_after. This allow clients to # correctly track the state of the room. msc4222_enabled: true + # sticky events for MatrixRTC user state + msc4354_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no diff --git a/backend/playwright_homeserver.yaml b/backend/playwright_homeserver.yaml index 0d7b175c..a83247cd 100644 --- a/backend/playwright_homeserver.yaml +++ b/backend/playwright_homeserver.yaml @@ -38,6 +38,8 @@ experimental_features: # MSC4222 needed for syncv2 state_after. This allow clients to # correctly track the state of the room. msc4222_enabled: true + # sticky events for MatrixRTC user state + msc4354_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no diff --git a/config/otel_dev/README.md b/config/otel_dev/README.md index ea6c09a2..87c3da93 100644 --- a/config/otel_dev/README.md +++ b/config/otel_dev/README.md @@ -1,5 +1,12 @@ # OpenTelemetry Collector for development +## Edit: + +Open telemetry has been removed in: https://github.com/element-hq/element-call/pull/3586 +Check this PR to get back the implementation or to use it as reference to add it back. + +--- + This directory contains a docker compose file that starts a jaeger all-in-one instance with an in-memory database, along with a standalone OpenTelemetry collector that forwards traces into the jaeger. Jaeger has a built-in OpenTelemetry collector, but it can't be diff --git a/dev-backend-docker-compose.yml b/dev-backend-docker-compose.yml index c7591847..0efefd07 100644 --- a/dev-backend-docker-compose.yml +++ b/dev-backend-docker-compose.yml @@ -3,7 +3,7 @@ networks: services: auth-service: - image: ghcr.io/element-hq/lk-jwt-service:latest-ci + image: ghcr.io/element-hq/lk-jwt-service:pr_139 pull_policy: always hostname: auth-server environment: @@ -25,7 +25,7 @@ services: - ecbackend auth-service-1: - image: ghcr.io/element-hq/lk-jwt-service:latest-ci + image: ghcr.io/element-hq/lk-jwt-service:pr_139 pull_policy: always hostname: auth-server-1 environment: @@ -88,7 +88,7 @@ services: synapse: hostname: homeserver - image: docker.io/matrixdotorg/synapse:latest + image: ghcr.io/element-hq/synapse:pr-18968-dcb7678281bc02d4551043a6338fe5b7e6aa47ce pull_policy: always environment: - SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml @@ -106,7 +106,7 @@ services: synapse-1: hostname: homeserver-1 - image: docker.io/matrixdotorg/synapse:latest + image: ghcr.io/element-hq/synapse:pr-18968-dcb7678281bc02d4551043a6338fe5b7e6aa47ce pull_policy: always environment: - SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml diff --git a/docs/self-hosting.md b/docs/self-hosting.md index d15f2910..d6d46421 100644 --- a/docs/self-hosting.md +++ b/docs/self-hosting.md @@ -152,6 +152,40 @@ handle { } ``` +Using Haproxy, you can achieve this by: + +``` +# Frontend + # Match /livekit/sfu/ path + acl is_sfu path_beg -i /livekit/sfu/ + use_backend sfu_backend if is_sfu matrixrtc_domain + + acl is_mxrtc_auth path_beg -i /sfu/get + use_backend mxrtc_auth_backend if is_mxrtc_auth matrixrtc_domain + +# Backend +## MatrixRTC backend +backend sfu_backend + server livekit 127.0.0.1:7880 + http-request set-path %[path,regsub(^/livekit/sfu/,/)] + http-request set-header Host %[req.hdr(host)] + timeout server 120s + # WebSocket support + option forwardfor + option http-server-close + option http-buffer-request + +backend mxrtc_auth_backend + server sfu 127.0.0.1:8070 + http-request set-header Host %[req.hdr(host)] + timeout server 120s + # WebSocket support + option forwardfor + option http-server-close + option http-buffer-request + +``` + #### MatrixRTC backend announcement > [!IMPORTANT] diff --git a/knip.ts b/knip.ts index 7edfaf65..d23d42fe 100644 --- a/knip.ts +++ b/knip.ts @@ -34,10 +34,6 @@ export default { // then Knip will flag it as a false positive // https://github.com/webpro-nl/knip/issues/766 "@vector-im/compound-web", - // We need this so that TypeScript is happy with @livekit/track-processors. - // This might be a bug in the LiveKit repo but for now we fix it on the - // Element Call side. - "@types/dom-mediacapture-transform", "matrix-widget-api", ], ignoreExportsUsedInFile: true, diff --git a/locales/en/app.json b/locales/en/app.json index 1ff066ea..c70a0a49 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -116,6 +116,7 @@ "matrix_rtc_transport_missing": "The server is not configured to work with {{brand}}. Please contact your server admin (Domain: {{domain}}, Error Code: {{ errorCode }}).", "membership_manager": "Membership Manager Error", "membership_manager_description": "The Membership Manager had to shut down. This is caused by many consequtive failed network requests.", + "no_matrix_2_authorization_service": "Your authorization service for you media server (SFU) is not on the newest version", "open_elsewhere": "Opened in another tab", "open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page.", "room_creation_restricted": "Failed to create call", diff --git a/package.json b/package.json index 336dd5d7..04308630 100644 --- a/package.json +++ b/package.json @@ -42,20 +42,13 @@ "@codecov/vite-plugin": "^1.3.0", "@fontsource/inconsolata": "^5.1.0", "@fontsource/inter": "^5.1.0", - "@formatjs/intl-durationformat": "^0.7.0", + "@formatjs/intl-durationformat": "^0.9.0", "@formatjs/intl-segmenter": "^11.7.3", "@livekit/components-core": "^0.12.0", "@livekit/components-react": "^2.0.0", "@livekit/protocol": "^1.42.2", - "@livekit/track-processors": "^0.5.5", + "@livekit/track-processors": "^0.6.0 || ^0.7.1", "@mediapipe/tasks-vision": "^0.10.18", - "@opentelemetry/api": "^1.4.0", - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.203.0", - "@opentelemetry/resources": "^2.0.0", - "@opentelemetry/sdk-trace-base": "^2.0.0", - "@opentelemetry/sdk-trace-web": "^2.0.0", - "@opentelemetry/semantic-conventions": "^1.25.1", "@playwright/test": "^1.57.0", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-slider": "^1.1.2", @@ -69,7 +62,6 @@ "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.1", "@types/content-type": "^1.1.5", - "@types/dom-mediacapture-transform": "^0.1.11", "@types/grecaptcha": "^3.0.9", "@types/jsdom": "^21.1.7", "@types/lodash-es": "^4.17.12", @@ -104,7 +96,7 @@ "eslint-plugin-unicorn": "^56.0.0", "fetch-mock": "11.1.5", "global-jsdom": "^26.0.0", - "i18next": "^24.0.0", + "i18next": "^25.0.0", "i18next-browser-languagedetector": "^8.0.0", "i18next-parser": "^9.1.0", "jsdom": "^26.0.0", @@ -112,7 +104,7 @@ "livekit-client": "^2.13.0", "lodash-es": "^4.17.21", "loglevel": "^1.9.1", - "matrix-js-sdk": "matrix-org/matrix-js-sdk#2218ec4e3102e841ba3e794e1c492c0a5aa6c1c3", + "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^1.14.0", "node-stdlib-browser": "^1.3.1", "normalize.css": "^8.0.1", @@ -125,7 +117,7 @@ "qrcode": "^1.5.4", "react": "19", "react-dom": "19", - "react-i18next": "^15.0.0", + "react-i18next": "^16.0.0 <16.1.0", "react-router-dom": "^7.0.0", "react-use-measure": "^2.1.1", "rxjs": "^7.8.1", @@ -133,6 +125,7 @@ "typescript": "^5.8.3", "typescript-eslint-language-service": "^5.0.5", "unique-names-generator": "^4.6.0", + "uuid": "^13.0.0", "vaul": "^1.0.0", "vite": "^7.0.0", "vite-plugin-generate-file": "^0.3.0", diff --git a/playwright.config.ts b/playwright.config.ts index 391e746f..4fb86b95 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,5 +1,6 @@ /* Copyright 2025 New Vector Ltd. +Copyright 2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. diff --git a/playwright/fixtures/widget-user.ts b/playwright/fixtures/widget-user.ts index f1f738b7..c2f8ca23 100644 --- a/playwright/fixtures/widget-user.ts +++ b/playwright/fixtures/widget-user.ts @@ -1,5 +1,6 @@ /* Copyright 2025 New Vector Ltd. +Copyright 2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. @@ -17,6 +18,7 @@ import type { MatrixClient } from "matrix-js-sdk"; export type UserBaseFixture = { mxId: string; + displayName: string; page: Page; clientHandle: JSHandle; }; @@ -28,6 +30,7 @@ export type BaseWidgetSetup = { export interface MyFixtures { asWidget: BaseWidgetSetup; + callType: "room" | "dm"; } const PASSWORD = "foobarbaz1!"; @@ -145,25 +148,27 @@ async function registerUser( } export const widgetTest = test.extend({ - asWidget: async ({ browser, context }, pUse) => { + // allow per-test override: `widgetTest.use({ callType: "dm" })` + callType: ["room", { option: true }], + asWidget: async ({ browser, context, callType }, pUse) => { await context.route(`http://localhost:8081/config.json*`, async (route) => { await route.fulfill({ json: CONFIG_JSON }); }); - const userA = `brooks_${Date.now()}`; - const userB = `whistler_${Date.now()}`; + const brooksDisplayName = `brooks_${Date.now()}`; + const whistlerDisplayName = `whistler_${Date.now()}`; // Register users const { page: ewPage1, clientHandle: brooksClientHandle, mxId: brooksMxId, - } = await registerUser(browser, userA); + } = await registerUser(browser, brooksDisplayName); const { page: ewPage2, clientHandle: whistlerClientHandle, mxId: whistlerMxId, - } = await registerUser(browser, userB); + } = await registerUser(browser, whistlerDisplayName); // Invite the second user await ewPage1 @@ -171,37 +176,66 @@ export const widgetTest = test.extend({ .getByRole("button", { name: "New conversation" }) .click(); - await ewPage1.getByRole("menuitem", { name: "New Room" }).click(); - await ewPage1.getByRole("textbox", { name: "Name" }).fill("Welcome Room"); - await ewPage1.getByRole("button", { name: "Create room" }).click(); - await expect(ewPage1.getByText("You created this room.")).toBeVisible(); - await expect(ewPage1.getByText("Encryption enabled")).toBeVisible(); + if (callType === "room") { + await ewPage1.getByRole("menuitem", { name: "New Room" }).click(); + await ewPage1.getByRole("textbox", { name: "Name" }).fill("Welcome Room"); + await ewPage1.getByRole("button", { name: "Create room" }).click(); + await expect(ewPage1.getByText("You created this room.")).toBeVisible(); + await expect(ewPage1.getByText("Encryption enabled")).toBeVisible(); - await ewPage1 - .getByRole("button", { name: "Invite to this room", exact: true }) - .click(); - await expect( - ewPage1.getByRole("heading", { name: "Invite to Welcome Room" }), - ).toBeVisible(); + await ewPage1 + .getByRole("button", { name: "Invite to this room", exact: true }) + .click(); + await expect( + ewPage1.getByRole("heading", { name: "Invite to Welcome Room" }), + ).toBeVisible(); - // To get the invite textbox we need to specifically select within the - // dialog, since there is another textbox in the background (the message - // composer). In theory the composer shouldn't be visible to Playwright at - // all because the invite dialog has trapped focus, but the focus trap - // doesn't quite work right on Firefox. - await ewPage1.getByRole("dialog").getByRole("textbox").fill(whistlerMxId); - await ewPage1.getByRole("dialog").getByRole("textbox").click(); - await ewPage1.getByRole("button", { name: "Invite" }).click(); + // To get the invite textbox we need to specifically select within the + // dialog, since there is another textbox in the background (the message + // composer). In theory the composer shouldn't be visible to Playwright at + // all because the invite dialog has trapped focus, but the focus trap + // doesn't quite work right on Firefox. + await ewPage1.getByRole("dialog").getByRole("textbox").fill(whistlerMxId); + await ewPage1.getByRole("dialog").getByRole("textbox").click(); + await ewPage1.getByRole("button", { name: "Invite" }).click(); - // Accept the invite - await expect( - ewPage2.getByRole("option", { name: "Welcome Room" }), - ).toBeVisible(); - await ewPage2.getByRole("option", { name: "Welcome Room" }).click(); - await ewPage2.getByRole("button", { name: "Accept" }).click(); - await expect( - ewPage2.getByRole("main").getByRole("heading", { name: "Welcome Room" }), - ).toBeVisible(); + // Accept the invite + await expect( + ewPage2.getByRole("option", { name: "Welcome Room" }), + ).toBeVisible(); + await ewPage2.getByRole("option", { name: "Welcome Room" }).click(); + await ewPage2.getByRole("button", { name: "Accept" }).click(); + await expect( + ewPage2 + .getByRole("main") + .getByRole("heading", { name: "Welcome Room" }), + ).toBeVisible(); + } else if (callType === "dm") { + await ewPage1.getByRole("menuitem", { name: "Start chat" }).click(); + await ewPage1.getByRole("textbox", { name: "Search" }).click(); + await ewPage1.getByRole("textbox", { name: "Search" }).fill(whistlerMxId); + await ewPage1.getByRole("button", { name: "Go" }).click(); + + // Wait and send the first message to create the DM + await expect( + ewPage1.getByText(/Send your first message to invite/), + ).toBeVisible(); + + await ewPage1.locator(".mx_BasicMessageComposer_input > div").click(); + await ewPage1 + .getByRole("textbox", { name: "Send a message…" }) + .fill("Hello!"); + await ewPage1.getByRole("button", { name: "Send message" }).click(); + + await expect( + ewPage1.getByText("This is the beginning of your"), + ).toBeVisible(); + + // Accept the DM invite from brooks + // This how playwright record selects the DM invite in the room list + await ewPage2.getByRole("option", { name: "Open room" }).click(); + await ewPage2.getByRole("button", { name: "Start chatting" }).click(); + } // Renamed use to pUse, as a workaround for eslint error that was thinking this use was a react use. await pUse({ @@ -209,11 +243,13 @@ export const widgetTest = test.extend({ mxId: brooksMxId, page: ewPage1, clientHandle: brooksClientHandle, + displayName: brooksDisplayName, }, whistler: { mxId: whistlerMxId, page: ewPage2, clientHandle: whistlerClientHandle, + displayName: whistlerDisplayName, }, }); }, diff --git a/playwright/widget/voice-call-dm.spec.ts b/playwright/widget/voice-call-dm.spec.ts new file mode 100644 index 00000000..a7aed984 --- /dev/null +++ b/playwright/widget/voice-call-dm.spec.ts @@ -0,0 +1,212 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { expect, test } from "@playwright/test"; + +import { widgetTest } from "../fixtures/widget-user.ts"; + +widgetTest.use({ callType: "dm" }); + +widgetTest( + "Start a new voice call in DM as widget", + async ({ asWidget, browserName }) => { + test.skip( + browserName === "firefox", + "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", + ); + + test.slow(); // Triples the timeout + + const { brooks, whistler } = asWidget; + + await expect( + brooks.page.getByRole("button", { name: "Voice call" }), + ).toBeVisible(); + await brooks.page.getByRole("button", { name: "Voice call" }).click(); + + await expect( + brooks.page.getByRole("menuitem", { name: "Element Call" }), + ).toBeVisible(); + + await brooks.page.getByRole("menuitem", { name: "Element Call" }).click(); + + await expect( + brooks.page.locator('iframe[title="Element Call"]'), + ).toBeVisible(); + + const brooksFrame = brooks.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + + // We should show a ringing overlay, let's check for that + await expect( + brooksFrame.getByText(`Waiting for ${whistler.displayName} to join…`), + ).toBeVisible(); + + await expect(whistler.page.getByText("Incoming voice call")).toBeVisible(); + await whistler.page.getByRole("button", { name: "Accept" }).click(); + + await expect( + whistler.page.locator('iframe[title="Element Call"]'), + ).toBeVisible(); + + const whistlerFrame = whistler.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + + // ASSERT the button states for whistler (the callee) + { + // The only way to know if it is muted or not is to look at the data-kind attribute.. + const videoButton = whistlerFrame.getByTestId("incall_videomute"); + // video should be off by default in a voice call + await expect(videoButton).toHaveAttribute("aria-label", /^Start video$/); + + const audioButton = whistlerFrame.getByTestId("incall_mute"); + // audio should be on for the voice call + await expect(audioButton).toHaveAttribute( + "aria-label", + /^Mute microphone$/, + ); + } + + // ASSERT the button states for brools (the caller) + { + // The only way to know if it is muted or not is to look at the data-kind attribute.. + const videoButton = brooksFrame.getByTestId("incall_videomute"); + // video should be off by default in a voice call + await expect(videoButton).toHaveAttribute("aria-label", /^Start video$/); + + const audioButton = brooksFrame.getByTestId("incall_mute"); + // audio should be on for the voice call + await expect(audioButton).toHaveAttribute( + "aria-label", + /^Mute microphone$/, + ); + } + + // In order to confirm that the call is disconnected we will check that the message composer is shown again. + // So first we need to confirm that it is hidden when in the call. + await expect( + whistler.page.locator(".mx_BasicMessageComposer"), + ).not.toBeVisible(); + await expect( + brooks.page.locator(".mx_BasicMessageComposer"), + ).not.toBeVisible(); + + // ASSERT hanging up on one side ends the call for both + { + const hangupButton = brooksFrame.getByTestId("incall_leave"); + await hangupButton.click(); + } + + // The widget should be closed on both sides and the timeline should be back on screen + await expect( + whistler.page.locator(".mx_BasicMessageComposer"), + ).toBeVisible(); + await expect(brooks.page.locator(".mx_BasicMessageComposer")).toBeVisible(); + }, +); + +widgetTest( + "Start a new video call in DM as widget", + async ({ asWidget, browserName }) => { + test.skip( + browserName === "firefox", + "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", + ); + + test.slow(); // Triples the timeout + + const { brooks, whistler } = asWidget; + + await expect( + brooks.page.getByRole("button", { name: "Video call" }), + ).toBeVisible(); + await brooks.page.getByRole("button", { name: "Video call" }).click(); + + await expect( + brooks.page.getByRole("menuitem", { name: "Element Call" }), + ).toBeVisible(); + + await brooks.page.getByRole("menuitem", { name: "Element Call" }).click(); + + await expect( + brooks.page.locator('iframe[title="Element Call"]'), + ).toBeVisible(); + + const brooksFrame = brooks.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + + // We should show a ringing overlay, let's check for that + await expect( + brooksFrame.getByText(`Waiting for ${whistler.displayName} to join…`), + ).toBeVisible(); + + await expect(whistler.page.getByText("Incoming video call")).toBeVisible(); + await whistler.page.getByRole("button", { name: "Accept" }).click(); + + await expect( + whistler.page.locator('iframe[title="Element Call"]'), + ).toBeVisible(); + + const whistlerFrame = whistler.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + + // ASSERT the button states for whistler (the callee) + { + // The only way to know if it is muted or not is to look at the data-kind attribute.. + const videoButton = whistlerFrame.getByTestId("incall_videomute"); + // video should be on by default in a voice call + await expect(videoButton).toHaveAttribute("aria-label", /^Stop video$/); + + const audioButton = whistlerFrame.getByTestId("incall_mute"); + // audio should be on for the voice call + await expect(audioButton).toHaveAttribute( + "aria-label", + /^Mute microphone$/, + ); + } + + // ASSERT the button states for brools (the caller) + { + // The only way to know if it is muted or not is to look at the data-kind attribute.. + const videoButton = brooksFrame.getByTestId("incall_videomute"); + // video should be on by default in a voice call + await expect(videoButton).toHaveAttribute("aria-label", /^Stop video$/); + + const audioButton = brooksFrame.getByTestId("incall_mute"); + // audio should be on for the voice call + await expect(audioButton).toHaveAttribute( + "aria-label", + /^Mute microphone$/, + ); + } + + // In order to confirm that the call is disconnected we will check that the message composer is shown again. + // So first we need to confirm that it is hidden when in the call. + await expect( + whistler.page.locator(".mx_BasicMessageComposer"), + ).not.toBeVisible(); + await expect( + brooks.page.locator(".mx_BasicMessageComposer"), + ).not.toBeVisible(); + + // ASSERT hanging up on one side ends the call for both + { + const hangupButton = brooksFrame.getByTestId("incall_leave"); + await hangupButton.click(); + } + + // The widget should be closed on both sides and the timeline should be back on screen + await expect( + whistler.page.locator(".mx_BasicMessageComposer"), + ).toBeVisible(); + await expect(brooks.page.locator(".mx_BasicMessageComposer")).toBeVisible(); + }, +); diff --git a/sdk/README.md b/sdk/README.md index 03801b83..91337f10 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -6,16 +6,40 @@ It allows to use matrixRTC in combination with livekit without relying on elemen This is done by instantiating the call view model and exposing some useful behaviors (observables) and methods. -This folder contains an example index.html file that showcases the sdk in use (hosted on localhost:8123 with a webserver ellowing cors (for example `npx serve -l 81234 --cors`)) as a godot engine HTML export template. +This folder contains an example index.html file that showcases the sdk in use (hosted on localhost:8123 with a webserver allowing cors (for example `npx serve -l 81234 --cors`)) as a godot engine HTML export template. + +## Getting started + +To get started run + +``` +yarn +yarn build:sdk +``` + +in the repository root. + +It will create a `dist` folder containing the compiled js file. + +This file needs to be hosted. Locally (via `npx serve -l 81234 --cors`) or on a remote server. + +Now you just need to add the widget to element web via: + +``` +/addwidget http://localhost:3000?widgetId=$matrix_widget_id&perParticipantE2EE=true&userId=$matrix_user_id&deviceId=$org.matrix.msc3819.matrix_device_id&baseUrl=$org.matrix.msc4039.matrix_base_url&roomId=$matrix_room_id +``` ## Widgets -The sdk mode is particularly interesting to be used in widgets where you do not need to pay attention to matrix login/cs api ... -To create a widget see the example index.html file in this folder. And add it to EW via: +The sdk mode is particularly interesting to be used in widgets. In widgets you do not need to pay attention to matrix login/cs api ... +To create a widget see the example `index.html` file in this folder. And add it to EW via: `/addwidget ` (see **url parameters** for more details on ``) ### url parameters +The url parameters are needed to pass initial data to the widget. They will automatically be used +by the matrixRTCSdk to start the postmessage widget api (communication between the client (e.g. Element Web) and the widget) + ``` widgetId = $matrix_widget_id perParticipantE2EE = true diff --git a/sdk/index.html b/sdk/index.html index 51110ebd..8883b9a3 100644 --- a/sdk/index.html +++ b/sdk/index.html @@ -1,7 +1,7 @@ - Godot MatrixRTC Widget + MatrixRTC Widget - - -
- - +
diff --git a/sdk/main.ts b/sdk/main.ts index 376674a4..c371587f 100644 --- a/sdk/main.ts +++ b/sdk/main.ts @@ -1,5 +1,5 @@ /* -Copyright 2025 Element Creations Ltd. +Copyright 2025-2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. @@ -99,14 +99,12 @@ export async function createMatrixRTCSdk( if (room === null) throw Error("could not get room from client"); const mediaDevices = new MediaDevices(scope); - const muteStates = new MuteStates(scope, mediaDevices, constant(true)); + const muteStates = new MuteStates(scope, mediaDevices, { + audioEnabled: true, + videoEnabled: true, + }); const slot = { application, id }; - const rtcSession = new MatrixRTCSession( - client, - room, - MatrixRTCSession.sessionMembershipsForSlot(room, slot), - slot, - ); + const rtcSession = new MatrixRTCSession(client, room, slot); const callViewModel = createCallViewModel$( scope, rtcSession, diff --git a/src/@types/dom-mediacapture-transform.d.ts b/src/@types/dom-mediacapture-transform.d.ts new file mode 100644 index 00000000..ed0a1e4a --- /dev/null +++ b/src/@types/dom-mediacapture-transform.d.ts @@ -0,0 +1,147 @@ +/* eslint-disable */ +// The contents of this file below the line are copied from +// @types/dom-mediacapture-transform, which is inlined here into Element Call so +// that we can avoid the package's dependency on @types/dom-webcodecs, which is +// broken in TypeScript 5.9. +// (https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/74294) +// If that issue is resolved, we can remove this file and return to depending on +// @types/dom-mediacapture-transform. +// ----------------------------------------------------------------------------- + +// This project is licensed under the MIT license. +// Copyrights are respective of each contributor listed at the beginning of each definition file. + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// In general, these types are only available behind a command line flag or an origin trial in +// Chrome 90+. + +// This API depends on WebCodecs. + +// Versioning: +// Until the above-mentioned spec is finalized, the major version number is 0. Although not +// necessary for version 0, consider incrementing the minor version number for breaking changes. + +// The following modify existing DOM types to allow defining type-safe APIs on audio and video tracks. + +/** Specialize MediaStreamTrack so that we can refer specifically to an audio track. */ +interface MediaStreamAudioTrack extends MediaStreamTrack { + readonly kind: "audio"; + clone(): MediaStreamAudioTrack; +} + +/** Specialize MediaStreamTrack so that we can refer specifically to a video track. */ +interface MediaStreamVideoTrack extends MediaStreamTrack { + readonly kind: "video"; + clone(): MediaStreamVideoTrack; +} + +/** Assert that getAudioTracks and getVideoTracks return the tracks with the appropriate kind. */ +interface MediaStream { + getAudioTracks(): MediaStreamAudioTrack[]; + getVideoTracks(): MediaStreamVideoTrack[]; +} + +// The following were originally generated from the spec using +// https://github.com/microsoft/TypeScript-DOM-lib-generator, then heavily modified. + +/** + * A track sink that is capable of exposing the unencoded frames from the track to a + * ReadableStream, and exposes a control channel for signals going in the oppposite direction. + */ +interface MediaStreamTrackProcessor { + /** + * Allows reading the frames flowing through the MediaStreamTrack provided to the constructor. + */ + readonly readable: ReadableStream; + /** Allows sending control signals to the MediaStreamTrack provided to the constructor. */ + readonly writableControl: WritableStream; +} + +declare var MediaStreamTrackProcessor: { + prototype: MediaStreamTrackProcessor; + + /** Constructor overrides based on the type of track. */ + new ( + init: MediaStreamTrackProcessorInit & { track: MediaStreamAudioTrack }, + ): MediaStreamTrackProcessor; + new ( + init: MediaStreamTrackProcessorInit & { track: MediaStreamVideoTrack }, + ): MediaStreamTrackProcessor; +}; + +interface MediaStreamTrackProcessorInit { + track: MediaStreamTrack; + /** + * If media frames are not read from MediaStreamTrackProcessor.readable quickly enough, the + * MediaStreamTrackProcessor will internally buffer up to maxBufferSize of the frames produced + * by the track. If the internal buffer is full, each time the track produces a new frame, the + * oldest frame in the buffer will be dropped and the new frame will be added to the buffer. + */ + maxBufferSize?: number | undefined; +} + +/** + * Takes video frames as input, and emits control signals that result from subsequent processing. + */ +interface MediaStreamTrackGenerator< + T extends AudioData | VideoFrame, +> extends MediaStreamTrack { + /** + * Allows writing media frames to the MediaStreamTrackGenerator, which is itself a + * MediaStreamTrack. When a frame is written to writable, the frame’s close() method is + * automatically invoked, so that its internal resources are no longer accessible from + * JavaScript. + */ + readonly writable: WritableStream; + /** + * Allows reading control signals sent from any sinks connected to the + * MediaStreamTrackGenerator. + */ + readonly readableControl: ReadableStream; +} + +type MediaStreamAudioTrackGenerator = MediaStreamTrackGenerator & + MediaStreamAudioTrack; +type MediaStreamVideoTrackGenerator = MediaStreamTrackGenerator & + MediaStreamVideoTrack; + +declare var MediaStreamTrackGenerator: { + prototype: MediaStreamTrackGenerator; + + /** Constructor overrides based on the type of track. */ + new ( + init: MediaStreamTrackGeneratorInit & { + kind: "audio"; + signalTarget?: MediaStreamAudioTrack | undefined; + }, + ): MediaStreamAudioTrackGenerator; + new ( + init: MediaStreamTrackGeneratorInit & { + kind: "video"; + signalTarget?: MediaStreamVideoTrack | undefined; + }, + ): MediaStreamVideoTrackGenerator; +}; + +interface MediaStreamTrackGeneratorInit { + kind: MediaStreamTrackGeneratorKind; + /** + * (Optional) track to which the MediaStreamTrackGenerator will automatically forward control + * signals. If signalTarget is provided and signalTarget.kind and kind do not match, the + * MediaStreamTrackGenerator’s constructor will raise an exception. + */ + signalTarget?: MediaStreamTrack | undefined; +} + +type MediaStreamTrackGeneratorKind = "audio" | "video"; + +type MediaStreamTrackSignalType = "request-frame"; + +interface MediaStreamTrackSignal { + signalType: MediaStreamTrackSignalType; +} diff --git a/src/RTCConnectionStats.tsx b/src/RTCConnectionStats.tsx index d51089cf..f62a1e4d 100644 --- a/src/RTCConnectionStats.tsx +++ b/src/RTCConnectionStats.tsx @@ -20,6 +20,7 @@ interface Props { audio?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats; video?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats; focusUrl?: string; + rtcBackendIdentity?: string; } const extractDomain = (url: string): string => { @@ -37,6 +38,7 @@ export const RTCConnectionStats: FC = ({ audio, video, focusUrl, + rtcBackendIdentity, ...rest }) => { const [showModal, setShowModal] = useState(false); @@ -71,6 +73,9 @@ export const RTCConnectionStats: FC = ({
+ + rtcBackendIdentity:{rtcBackendIdentity} + {focusUrl && (
diff --git a/src/UrlParams.test.ts b/src/UrlParams.test.ts index faba394f..ec92ee89 100644 --- a/src/UrlParams.test.ts +++ b/src/UrlParams.test.ts @@ -1,5 +1,6 @@ /* Copyright 2023, 2024 New Vector Ltd. +Copyright 2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. @@ -256,8 +257,6 @@ describe("UrlParams", () => { skipLobby: false, returnToLobby: false, sendNotificationType: "notification", - defaultAudioEnabled: true, - defaultVideoEnabled: true, }); it("use no-intent-defaults with unknown intent", () => { expect(computeUrlParams()).toMatchObject(noIntentDefaults); @@ -395,8 +394,6 @@ describe("UrlParams", () => { expect.any(Object), "configuration:", expect.any(Object), - "intentAndPlatformDerivedConfiguration:", - {}, ); }); }); diff --git a/src/UrlParams.ts b/src/UrlParams.ts index f78841fb..f8ee22fb 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -1,5 +1,6 @@ /* Copyright 2022-2024 New Vector Ltd. +Copyright 2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. @@ -246,24 +247,13 @@ export interface UrlConfiguration { callIntent?: RTCCallIntent; } -interface IntentAndPlatformDerivedConfiguration { - defaultAudioEnabled?: boolean; - defaultVideoEnabled?: boolean; -} -interface IntentAndPlatformDerivedConfiguration { - defaultAudioEnabled?: boolean; - defaultVideoEnabled?: boolean; -} // If you need to add a new flag to this interface, prefer a name that describes // a specific behavior (such as 'confineToRoom'), rather than one that describes // the situations that call for this behavior ('isEmbedded'). This makes it // clearer what each flag means, and helps us avoid coupling Element Call's // behavior to the needs of specific consumers. -export interface UrlParams - extends UrlProperties, - UrlConfiguration, - IntentAndPlatformDerivedConfiguration {} +export interface UrlParams extends UrlProperties, UrlConfiguration {} // This is here as a stopgap, but what would be far nicer is a function that // takes a UrlParams and returns a query string. That would enable us to @@ -463,29 +453,6 @@ export const computeUrlParams = (search = "", hash = ""): UrlParams => { }; } - const intentAndPlatformDerivedConfiguration: IntentAndPlatformDerivedConfiguration = - {}; - // Desktop also includes web. Its anything that is not mobile. - const desktopMobile = platform === "desktop" ? "desktop" : "mobile"; - switch (desktopMobile) { - case "desktop": - case "mobile": - switch (intent) { - case UserIntent.StartNewCall: - case UserIntent.JoinExistingCall: - case UserIntent.StartNewCallDM: - case UserIntent.JoinExistingCallDM: - intentAndPlatformDerivedConfiguration.defaultAudioEnabled = true; - intentAndPlatformDerivedConfiguration.defaultVideoEnabled = true; - break; - case UserIntent.StartNewCallDMVoice: - case UserIntent.JoinExistingCallDMVoice: - intentAndPlatformDerivedConfiguration.defaultAudioEnabled = true; - intentAndPlatformDerivedConfiguration.defaultVideoEnabled = false; - break; - } - } - const properties: UrlProperties = { widgetId, parentUrl, @@ -550,15 +517,12 @@ export const computeUrlParams = (search = "", hash = ""): UrlParams => { properties, "configuration:", configuration, - "intentAndPlatformDerivedConfiguration:", - intentAndPlatformDerivedConfiguration, ); return { ...properties, ...intentPreset, ...pickBy(configuration, (v?: unknown) => v !== undefined), - ...intentAndPlatformDerivedConfiguration, }; }; diff --git a/src/__snapshots__/AppBar.test.tsx.snap b/src/__snapshots__/AppBar.test.tsx.snap index fe61d09b..c769ec12 100644 --- a/src/__snapshots__/AppBar.test.tsx.snap +++ b/src/__snapshots__/AppBar.test.tsx.snap @@ -13,7 +13,7 @@ exports[`AppBar > renders 1`] = ` class="nav leftNav" >
); + const allConnections = useBehavior(vm.allConnections$); + return (
= ({ onDismiss={closeSettings} tab={settingsTab} onTabChange={setSettingsTab} - // TODO expose correct data to setttings modal - livekitRooms={[]} + livekitRooms={allConnections + .getConnections() + .map((connectionItem) => ({ + room: connectionItem.livekitRoom, + // TODO compute is local or tag it in the livekit room items already + isLocal: undefined, + url: connectionItem.transport.livekit_service_url, + }))} /> )} diff --git a/src/room/RoomPage.tsx b/src/room/RoomPage.tsx index e9527e03..2aee4d53 100644 --- a/src/room/RoomPage.tsx +++ b/src/room/RoomPage.tsx @@ -1,5 +1,6 @@ /* Copyright 2021-2024 New Vector Ltd. +Copyright 2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. @@ -20,8 +21,6 @@ import { CheckIcon, UnknownSolidIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; -import { useObservable } from "observable-hooks"; -import { map } from "rxjs"; import { useClientLegacy } from "../ClientContext"; import { ErrorPage, FullScreenView, LoadingPage } from "../FullScreenView"; @@ -44,10 +43,12 @@ import { ErrorView } from "../ErrorView"; import { useMediaDevices } from "../MediaDevicesContext"; import { MuteStates } from "../state/MuteStates"; import { ObservableScope } from "../state/ObservableScope"; +import { calculateInitialMuteState } from "../state/initialMuteState.ts"; export const RoomPage: FC = () => { + const urlParams = useUrlParams(); const { confineToRoom, appPrompt, preload, header, displayName, skipLobby } = - useUrlParams(); + urlParams; const { t } = useTranslation(); const { roomAlias, roomId, viaServers } = useRoomIdentifier(); @@ -68,15 +69,22 @@ export const RoomPage: FC = () => { const devices = useMediaDevices(); const [muteStates, setMuteStates] = useState(null); - const joined$ = useObservable( - (inputs$) => inputs$.pipe(map(([joined]) => joined)), - [joined], - ); + useEffect(() => { const scope = new ObservableScope(); - setMuteStates(new MuteStates(scope, devices, joined$)); + setMuteStates( + new MuteStates( + scope, + devices, + calculateInitialMuteState( + urlParams.skipLobby, + urlParams.callIntent, + widget !== null, + ), + ), + ); return (): void => scope.end(); - }, [devices, joined$]); + }, [devices, urlParams]); useEffect(() => { // If we've finished loading, are not already authed and we've been given a display name as diff --git a/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap b/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap index 73a6df12..3f02a49a 100644 --- a/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap +++ b/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap @@ -108,7 +108,7 @@ exports[`ConnectionLostError: Action handling should reset error state 1`] = ` class="error" >
rendering > renders 1`] = ` tabindex="0" > rendering > renders 1`] = ` data-show="false" >
rendering > renders 1`] = ` Only works while using app

))}

{t("developer_mode.environment_variables")}

{JSON.stringify(env, null, 2)}
diff --git a/src/settings/__snapshots__/DeveloperSettingsTab.test.tsx.snap b/src/settings/__snapshots__/DeveloperSettingsTab.test.tsx.snap index ef3db126..2ee3710b 100644 --- a/src/settings/__snapshots__/DeveloperSettingsTab.test.tsx.snap +++ b/src/settings/__snapshots__/DeveloperSettingsTab.test.tsx.snap @@ -24,7 +24,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` class="field inputField" > renders and matches snapshot 1`] = ` class="field checkboxField" > @@ -81,7 +81,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` class="field checkboxField" > @@ -118,7 +118,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` class="field checkboxField" > @@ -156,7 +156,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` class="field checkboxField" > @@ -195,7 +195,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` > @@ -203,9 +203,9 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` class="_controls_17lij_8" > renders and matches snapshot 1`] = `
Currently, no overwrite is set. Url from well-known or config is used. @@ -234,20 +234,20 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` class="_inline-field-control_19upo_44" >
@@ -256,13 +256,13 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` > Compatible with old versions of EC that do not support multi SFU @@ -275,19 +275,19 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` class="_inline-field-control_19upo_44" >
@@ -296,13 +296,13 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` > Compatible with homeservers that do not support sticky events (but all other EC clients are v0.17.0 or later) @@ -315,19 +315,19 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` class="_inline-field-control_19upo_44" >
@@ -336,59 +336,91 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` > Compatible only with homservers supporting sticky events and all EC clients v0.17.0 or later
-

- LiveKit SFU: wss://local-sfu.example.org -

-

- ws-url: - wss://local-sfu.example.org/ -

-

- LiveKit Server Info - ( - local - ) -

-
-    {
+    

+ LiveKit SFU: wss://local-sfu.example.org +

+

+ ws-url: + wss://local-sfu.example.org/ +

+

+ LiveKit Server Info + ( + local + ) +

+
+      {
   "region": "local",
   "version": "1.2.3"
 }
-    local-metadata
-  
-

- LiveKit SFU: wss://remote-sfu.example.org -

-

- LiveKit Server Info - ( - remote - ) -

-
+    

+ Local Participant +

+
+      localParticipantIdentity
+    
+

+ Remote Participants +

+
    +
+
- { +

+ LiveKit SFU: wss://remote-sfu.example.org +

+

+ LiveKit Server Info + ( + remote + ) +

+
+      {
   "region": "remote",
   "version": "4.5.6"
 }
-    remote-metadata
-  
+ remote-metadata + +

+ Local Participant +

+
+      localParticipantIdentity
+    
+

+ Remote Participants +

+
    +

Environment variables

diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 03008dca..917c79f1 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -131,7 +131,13 @@ export const alwaysShowIphoneEarpiece = new Setting( export enum MatrixRTCMode { Legacy = "legacy", - Compatibil = "compatibil", + Compatibility = "compatibility", + /** This implies using + * - sticky events + * - hashed RTC backend identity + * - the new endpoint for the jwt token on the local membership (remote memberships will always try the new jwt endpoint first -> then the legacy one) + * - use the hashed identity for the local membership + */ Matrix_2_0 = "matrix_2_0", } diff --git a/src/settings/submit-rageshake.ts b/src/settings/submit-rageshake.ts index bfd55126..276d8e60 100644 --- a/src/settings/submit-rageshake.ts +++ b/src/settings/submit-rageshake.ts @@ -17,7 +17,6 @@ import { type CryptoApi } from "matrix-js-sdk/lib/crypto-api"; import { getLogsForReport } from "./rageshake"; import { useClient } from "../ClientContext"; import { Config } from "../config/Config"; -import { ElementCallOpenTelemetry } from "../otel/otel"; import { type RageshakeRequestModal } from "../room/RageshakeRequestModal"; import { getUrlParams } from "../UrlParams"; @@ -274,14 +273,6 @@ export function useSubmitRageshake( for (const entry of logs) { body.append("compressed-log", await gzip(entry.lines), entry.id); } - - body.append( - "file", - await gzip( - ElementCallOpenTelemetry.instance.rageshakeProcessor!.dump(), - ), - "traces.json.gz", - ); } if (opts.rageshakeRequestId) { diff --git a/src/state/CallViewModel/CallViewModel.test.ts b/src/state/CallViewModel/CallViewModel.test.ts index 3205c07f..376d8986 100644 --- a/src/state/CallViewModel/CallViewModel.test.ts +++ b/src/state/CallViewModel/CallViewModel.test.ts @@ -78,11 +78,14 @@ vi.mock("../e2ee/matrixKeyProvider"); const getUrlParams = vi.hoisted(() => vi.fn(() => ({}))); vi.mock("../UrlParams", () => ({ getUrlParams })); -vi.mock("../rtcSessionHelpers", async (importOriginal) => ({ - ...(await importOriginal()), - makeTransport: async (): Promise => - Promise.resolve(exampleTransport), -})); +vi.mock( + "../state/CallViewModel/localMember/localTransport", + async (importOriginal) => ({ + ...(await importOriginal()), + makeTransport: async (): Promise => + Promise.resolve(exampleTransport), + }), +); const yesNo = { y: true, @@ -232,7 +235,7 @@ const mockLegacyRingEvent = {} as { event_id: string } & ICallNotifyContent; describe.each([ [MatrixRTCMode.Legacy], - [MatrixRTCMode.Compatibil], + [MatrixRTCMode.Compatibility], [MatrixRTCMode.Matrix_2_0], ])("CallViewModel (%s mode)", (mode) => { const withCallViewModel = withCallViewModelInMode(mode); @@ -1255,11 +1258,6 @@ describe.each([ y: () => { rtcSession.membershipStatus = Status.Connected; }, - n: () => { - // NOTE: This was removed in https://github.com/matrix-org/matrix-js-sdk/pull/5103 accidentally. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - rtcSession.membershipStatus = "Reconnecting" as any; - }, }); schedule(probablyLeftMarbles, { y: () => { diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index 5324c65d..0f212101 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -41,10 +41,13 @@ import { } from "rxjs"; import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { + MembershipManagerEvent, type LivekitTransport, type MatrixRTCSession, } from "matrix-js-sdk/lib/matrixrtc"; import { type IWidgetApiRequest } from "matrix-widget-api"; +import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; +import { v4 as uuidv4 } from "uuid"; import { LocalUserMediaViewModel, @@ -98,7 +101,7 @@ import { type SpotlightLandscapeLayoutMedia, type SpotlightPortraitLayoutMedia, } from "../layout-types.ts"; -import { ElementCallError } from "../../utils/errors.ts"; +import { ElementCallError, UnknownCallError } from "../../utils/errors.ts"; import { type ObservableScope } from "../ObservableScope.ts"; import { createHomeserverConnected$ } from "./localMember/HomeserverConnected.ts"; import { @@ -106,13 +109,19 @@ import { enterRTCSession, TransportState, } from "./localMember/LocalMember.ts"; -import { createLocalTransport$ } from "./localMember/LocalTransport.ts"; +import { + createLocalTransport$, + JwtEndpointVersion, +} from "./localMember/LocalTransport.ts"; import { createMemberships$, membershipsAndTransports$, } from "../SessionBehaviors.ts"; import { ECConnectionFactory } from "./remoteMembers/ConnectionFactory.ts"; -import { createConnectionManager$ } from "./remoteMembers/ConnectionManager.ts"; +import { + type ConnectionManagerData, + createConnectionManager$, +} from "./remoteMembers/ConnectionManager.ts"; import { createMatrixLivekitMembers$, type TaggedParticipant, @@ -261,6 +270,7 @@ export interface CallViewModel { * multiple devices. */ participantCount$: Behavior; + allConnections$: Behavior; /** Participants sorted by livekit room so they can be used in the audio rendering */ livekitRoomItems$: Behavior; userMedia$: Behavior; @@ -381,8 +391,11 @@ export function createCallViewModel$( trackProcessorState$: Behavior, ): CallViewModel { const client = matrixRoom.client; - const userId = client.getUserId()!; - const deviceId = client.getDeviceId()!; + const userId = client.getUserId(); + const deviceId = client.getDeviceId(); + if (!(userId && deviceId)) + throw new UnknownCallError(new Error("userId and deviceId are required")); + const livekitKeyProvider = getE2eeKeyProvider( options.encryptionSystem, matrixRTCSession, @@ -415,11 +428,37 @@ export function createCallViewModel$( memberships$, ); + const ownMembershipIdentity: CallMembershipIdentityParts = { + userId, + deviceId, + // This will only be consumed by the sticky membership manager. So it has no impact on legacy calls. + memberId: uuidv4(), + }; + const localTransport$ = createLocalTransport$({ scope: scope, memberships$: memberships$, + ownMembershipIdentity, client, + delayId$: scope.behavior( + ( + fromEvent( + matrixRTCSession, + MembershipManagerEvent.DelayIdChanged, + ) as Observable + ).pipe(map((v) => v ?? null)), + matrixRTCSession.delayId ?? null, + ), roomId: matrixRoom.roomId, + forceJwtEndpoint$: scope.behavior( + matrixRTCMode$.pipe( + map((v) => + v === MatrixRTCMode.Matrix_2_0 + ? JwtEndpointVersion.Matrix_2_0 + : JwtEndpointVersion.Legacy, + ), + ), + ), useOldestMember$: scope.behavior( matrixRTCMode$.pipe(map((v) => v === MatrixRTCMode.Legacy)), ), @@ -439,30 +478,20 @@ export function createCallViewModel$( const connectionManager = createConnectionManager$({ scope: scope, connectionFactory: connectionFactory, - inputTransports$: scope.behavior( - combineLatest( - [ - localTransport$.pipe( - catchError((e: unknown) => { - logger.info( - "dont pass local transport to createConnectionManager$. localTransport$ threw an error", - e, - ); - return of(null); - }), - ), - membershipsAndTransports.transports$, - ], - (localTransport, transports) => { - const localTransportAsArray = localTransport ? [localTransport] : []; - return transports.mapInner((transports) => [ - ...localTransportAsArray, - ...transports, - ]); - }, + localTransport$: scope.behavior( + localTransport$.pipe( + catchError((e: unknown) => { + logger.info( + "could not pass local transport to createConnectionManager$. localTransport$ threw an error", + e, + ); + return of(null); + }), ), ), - logger, + remoteTransports$: membershipsAndTransports.transports$, + logger: logger, + ownMembershipIdentity, }); const matrixLivekitMembers$ = createMatrixLivekitMembers$({ @@ -493,6 +522,7 @@ export function createCallViewModel$( joinMatrixRTC: (transport: LivekitTransport) => { return enterRTCSession( matrixRTCSession, + ownMembershipIdentity, transport, connectOptions$.value, ); @@ -604,15 +634,14 @@ export function createCallViewModel$( ), ); + const allConnections$ = scope.behavior( + connectionManager.connectionManagerData$.pipe(map((d) => d.value)), + ); const livekitRoomItems$ = scope.behavior( matrixLivekitMembers$.pipe( - tap((val) => { - logger.debug("matrixLivekitMembers$ updated", val.value); - }), - switchMap((membersWithEpoch) => { - const members = membersWithEpoch.value; + switchMap((members) => { const a$ = combineLatest( - members.map((member) => + members.value.map((member) => combineLatest([member.connection$, member.participant.value$]).pipe( map(([connection, participant]) => { // do not render audio for local participant @@ -685,29 +714,29 @@ export function createCallViewModel$( generateItems( function* ([ localMatrixLivekitMember, - { value: matrixLivekitMembers }, + matrixLivekitMembers, duplicateTiles, ]) { - let localParticipantId: string | undefined = undefined; + let localUserMediaId: string | undefined = undefined; // add local member if available if (localMatrixLivekitMember) { const { userId, participant, connection$, membership$ } = localMatrixLivekitMember; - localParticipantId = `${userId}:${membership$.value.deviceId}`; // should be membership$.value.membershipID which is not optional - // const participantId = membership$.value.membershipID; - if (localParticipantId) { - for (let dup = 0; dup < 1 + duplicateTiles; dup++) { - yield { - keys: [ - dup, - localParticipantId, - userId, - participant satisfies TaggedParticipant as TaggedParticipant, // Widen the type safely - connection$, - ], - data: undefined, - }; - } + + localUserMediaId = `${userId}:${membership$.value.deviceId}`; + const rtcBackendIdentity = membership$.value.rtcBackendIdentity; + for (let dup = 0; dup < 1 + duplicateTiles; dup++) { + yield { + keys: [ + dup, + localUserMediaId, + userId, + participant satisfies TaggedParticipant as TaggedParticipant, // Widen the type safely + connection$, + rtcBackendIdentity, + ], + data: undefined, + }; } } // add remote members that are available @@ -716,13 +745,22 @@ export function createCallViewModel$( participant, connection$, membership$, - } of matrixLivekitMembers) { - const participantId = `${userId}:${membership$.value.deviceId}`; - if (participantId === localParticipantId) continue; - // const participantId = membership$.value?.identity; + } of matrixLivekitMembers.value) { + const userMediaId = `${userId}:${membership$.value.deviceId}`; + const rtcBackendIdentity = membership$.value.rtcBackendIdentity; + // skip local user as we added them manually before + if (userMediaId === localUserMediaId) continue; + for (let dup = 0; dup < 1 + duplicateTiles; dup++) { yield { - keys: [dup, participantId, userId, participant, connection$], + keys: [ + dup, + userMediaId, + userId, + participant, + connection$, + rtcBackendIdentity, + ], data: undefined, }; } @@ -732,10 +770,11 @@ export function createCallViewModel$( scope, _data$, dup, - participantId, + userMediaId, userId, participant, connection$, + rtcBackendIdentity, ) => { const livekitRoom$ = scope.behavior( connection$.pipe(map((c) => c?.livekitRoom)), @@ -751,8 +790,9 @@ export function createCallViewModel$( return new UserMedia( scope, - `${participantId}:${dup}`, + `${userMediaId}:${dup}`, userId, + rtcBackendIdentity, participant, options.encryptionSystem, livekitRoom$, @@ -761,8 +801,8 @@ export function createCallViewModel$( localMembership.reconnecting$, displayName$, matrixMemberMetadataStore.createAvatarUrlBehavior$(userId), - handsRaised$.pipe(map((v) => v[participantId]?.time ?? null)), - reactions$.pipe(map((v) => v[participantId] ?? undefined)), + handsRaised$.pipe(map((v) => v[userMediaId]?.time ?? null)), + reactions$.pipe(map((v) => v[userMediaId] ?? undefined)), ); }, ), @@ -1503,6 +1543,7 @@ export function createCallViewModel$( ), null, ), + allConnections$, participantCount$: participantCount$, handsRaised$: handsRaised$, reactions$: reactions$, diff --git a/src/state/CallViewModel/localMember/LocalMember.test.ts b/src/state/CallViewModel/localMember/LocalMember.test.ts index 0d77611b..af12c98b 100644 --- a/src/state/CallViewModel/localMember/LocalMember.test.ts +++ b/src/state/CallViewModel/localMember/LocalMember.test.ts @@ -24,6 +24,7 @@ import { mockLivekitRoom, mockMuteStates, withTestScheduler, + ownMemberMock, } from "../../../utils/test"; import { TransportState, @@ -38,6 +39,7 @@ import { constant } from "../../Behavior"; import { ConnectionManagerData } from "../remoteMembers/ConnectionManager"; import { ConnectionState, type Connection } from "../remoteMembers/Connection"; import { type Publisher } from "./Publisher"; +import { type LocalTransportWithSFUConfig } from "./LocalTransport"; const MATRIX_RTC_MODE = MatrixRTCMode.Legacy; const getUrlParams = vi.hoisted(() => vi.fn(() => ({}))); @@ -103,11 +105,12 @@ describe("LocalMembership", () => { getOldestMembership: vi.fn().mockReturnValue({ getPreferredFoci: vi.fn().mockReturnValue([focusFromOlderMembership]), }), - joinRoomSession: vi.fn(), + joinRTCSession: vi.fn(), }) as unknown as MatrixRTCSession; enterRTCSession( mockedSession, + ownMemberMock, { livekit_alias: "roomId", livekit_service_url: "http://my-well-known-service-url.com", @@ -119,7 +122,12 @@ describe("LocalMembership", () => { }, ); - expect(mockedSession.joinRoomSession).toHaveBeenLastCalledWith( + expect(mockedSession.joinRTCSession).toHaveBeenLastCalledWith( + { + deviceId: "DEVICE", + memberId: "@alice:example.org:DEVICE", + userId: "@alice:example.org", + }, [ { livekit_alias: "roomId", @@ -161,11 +169,12 @@ describe("LocalMembership", () => { }, memberships: [], getFocusInUse: vi.fn(), - joinRoomSession: vi.fn(), + joinRTCSession: vi.fn(), }) as unknown as MatrixRTCSession; enterRTCSession( mockedSession, + ownMemberMock, { livekit_alias: "roomId", livekit_service_url: "http://my-well-known-service-url.com", @@ -204,10 +213,11 @@ describe("LocalMembership", () => { it("throws error on missing RTC config error", () => { withTestScheduler(({ scope, hot, expectObservable }) => { - const localTransport$ = scope.behavior( - hot("1ms #", {}, new MatrixRTCTransportMissingError("domain.com")), - null, - ); + const localTransport$ = + scope.behavior( + hot("1ms #", {}, new MatrixRTCTransportMissingError("domain.com")), + null, + ); // we do not need any connection data since we want to fail before reaching that. const mockConnectionManager = { @@ -235,11 +245,23 @@ describe("LocalMembership", () => { }); const aTransport = { - livekit_service_url: "a", - } as LivekitTransport; + transport: { + livekit_service_url: "a", + } as LivekitTransport, + sfuConfig: { + url: "sfu-url", + jwt: "sfu-token", + }, + } as LocalTransportWithSFUConfig; const bTransport = { - livekit_service_url: "b", - } as LivekitTransport; + transport: { + livekit_service_url: "b", + } as LivekitTransport, + sfuConfig: { + url: "sfu-url", + jwt: "sfu-token", + }, + } as LocalTransportWithSFUConfig; const connectionTransportAConnected = { livekitRoom: mockLivekitRoom({ @@ -249,7 +271,7 @@ describe("LocalMembership", () => { } as unknown as LocalParticipant, }), state$: constant(ConnectionState.LivekitConnected), - transport: aTransport, + transport: aTransport.transport, } as unknown as Connection; const connectionTransportAConnecting = { ...connectionTransportAConnected, @@ -258,11 +280,11 @@ describe("LocalMembership", () => { } as unknown as Connection; const connectionTransportBConnected = { state$: constant(ConnectionState.LivekitConnected), - transport: bTransport, + transport: bTransport.transport, livekitRoom: mockLivekitRoom({}), } as unknown as Connection; - it("recreates publisher if new connection is used and ENDS always unpublish and end tracks", async () => { + it("recreates publisher if new connection is used, always unpublish and end tracks", async () => { const scope = new ObservableScope(); const localTransport$ = new BehaviorSubject(aTransport); @@ -310,8 +332,12 @@ describe("LocalMembership", () => { expect(publishers[1].stopTracks).not.toHaveBeenCalled(); expect(publishers[0].stopPublishing).toHaveBeenCalled(); expect(publishers[1].stopPublishing).not.toHaveBeenCalled(); - expect(publisherFactory.mock.calls[0][0].transport).toBe(aTransport); - expect(publisherFactory.mock.calls[1][0].transport).toBe(bTransport); + expect(publisherFactory.mock.calls[0][0].transport).toBe( + aTransport.transport, + ); + expect(publisherFactory.mock.calls[1][0].transport).toBe( + bTransport.transport, + ); scope.end(); await flushPromises(); // stop all tracks after ending scopes @@ -383,7 +409,8 @@ describe("LocalMembership", () => { const scope = new ObservableScope(); const connectionManagerData = new ConnectionManagerData(); - const localTransport$ = new BehaviorSubject(null); + const localTransport$ = + new BehaviorSubject(null); const connectionManagerData$ = new BehaviorSubject( new Epoch(connectionManagerData), ); @@ -460,7 +487,7 @@ describe("LocalMembership", () => { }); ( - connectionManagerData2.getConnectionForTransport(aTransport)! + connectionManagerData2.getConnectionForTransport(aTransport.transport)! .state$ as BehaviorSubject ).next(ConnectionState.LivekitConnected); expect(localMembership.localMemberState$.value).toStrictEqual({ diff --git a/src/state/CallViewModel/localMember/LocalMember.ts b/src/state/CallViewModel/localMember/LocalMember.ts index dc22db23..4749e942 100644 --- a/src/state/CallViewModel/localMember/LocalMember.ts +++ b/src/state/CallViewModel/localMember/LocalMember.ts @@ -36,6 +36,7 @@ import { } from "rxjs"; import { type Logger } from "matrix-js-sdk/lib/logger"; import { deepCompare } from "matrix-js-sdk/lib/utils"; +import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type Behavior } from "../../Behavior.ts"; import { type IConnectionManager } from "../remoteMembers/ConnectionManager.ts"; @@ -60,6 +61,7 @@ import { } from "../remoteMembers/Connection.ts"; import { type HomeserverConnected } from "./HomeserverConnected.ts"; import { and$ } from "../../../utils/observable.ts"; +import { type LocalTransportWithSFUConfig } from "./LocalTransport.ts"; export enum TransportState { /** Not even a transport is available to the LocalMembership */ @@ -125,7 +127,7 @@ interface Props { createPublisherFactory: (connection: Connection) => Publisher; joinMatrixRTC: (transport: LivekitTransport) => void; homeserverConnected: HomeserverConnected; - localTransport$: Behavior; + localTransport$: Behavior; matrixRTCSession: Pick< MatrixRTCSession, "updateCallIntent" | "leaveRoomSession" @@ -233,7 +235,9 @@ export const createLocalMembership$ = ({ return null; } - return connectionData.getConnectionForTransport(localTransport); + return connectionData.getConnectionForTransport( + localTransport.transport, + ); }), tap((connection) => { logger.info( @@ -532,7 +536,7 @@ export const createLocalMembership$ = ({ if (!shouldConnect) return; try { - joinMatrixRTC(transport); + joinMatrixRTC(transport.transport); } catch (error) { logger.error("Error entering RTC session", error); if (error instanceof Error) @@ -551,7 +555,12 @@ export const createLocalMembership$ = ({ ); const participant$ = scope.behavior( - localConnection$.pipe(map((c) => c?.livekitRoom?.localParticipant ?? null)), + localConnection$.pipe( + map((c) => c?.livekitRoom?.localParticipant ?? null), + tap((p) => { + logger.debug("participant$ updated:", p?.identity); + }), + ), ); // Pause upstream of all local media tracks when we're disconnected from @@ -686,18 +695,19 @@ interface EnterRTCSessionOptions { * - Handles retries (fails only after several attempts) * * @param rtcSession - The MatrixRTCSession to join. + * @param ownMembershipIdentity - Options for entering the RTC session. * @param transport - The LivekitTransport to use for this session. - * @param options - Options for entering the RTC session. - * @param options.encryptMedia - Whether to encrypt media. - * @param options.matrixRTCMode - The Matrix RTC mode to use. + * @param options - `encryptMedia`: Whether to encrypt media `matrixRTCMode`: The Matrix RTC mode to use. * @throws If the widget could not send ElementWidgetActions.JoinCall action. */ // Exported for unit testing export function enterRTCSession( rtcSession: MatrixRTCSession, + ownMembershipIdentity: CallMembershipIdentityParts, transport: LivekitTransport, - { encryptMedia, matrixRTCMode }: EnterRTCSessionOptions, + options: EnterRTCSessionOptions, ): void { + const { encryptMedia, matrixRTCMode } = options; PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId); @@ -709,10 +719,13 @@ export function enterRTCSession( const useDeviceSessionMemberEvents = features?.feature_use_device_session_member_events; const { sendNotificationType: notificationType, callIntent } = getUrlParams(); - const multiSFU = matrixRTCMode !== MatrixRTCMode.Legacy; + const multiSFU = + matrixRTCMode === MatrixRTCMode.Compatibility || + matrixRTCMode === MatrixRTCMode.Matrix_2_0; // Multi-sfu does not need a preferred foci list. just the focus that is actually used. // TODO where/how do we track errors originating from the ongoing rtcSession? - rtcSession.joinRoomSession( + rtcSession.joinRTCSession( + ownMembershipIdentity, multiSFU ? [] : [transport], multiSFU ? transport : undefined, { diff --git a/src/state/CallViewModel/localMember/LocalTransport.test.ts b/src/state/CallViewModel/localMember/LocalTransport.test.ts index 3e69bf2c..9199b51e 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.test.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.test.ts @@ -18,8 +18,8 @@ import { type CallMembership } from "matrix-js-sdk/lib/matrixrtc"; import { BehaviorSubject, lastValueFrom } from "rxjs"; import fetchMock from "fetch-mock"; -import { mockConfig, flushPromises } from "../../../utils/test"; -import { createLocalTransport$ } from "./LocalTransport"; +import { mockConfig, flushPromises, ownMemberMock } from "../../../utils/test"; +import { createLocalTransport$, JwtEndpointVersion } from "./LocalTransport"; import { constant } from "../../Behavior"; import { Epoch, ObservableScope } from "../../ObservableScope"; import { @@ -39,11 +39,35 @@ describe("LocalTransport", () => { }; let scope: ObservableScope; - beforeEach(() => { - scope = new ObservableScope(); - }); + beforeEach(() => (scope = new ObservableScope())); afterEach(() => scope.end()); + it("throws if config is missing", async () => { + const localTransport$ = createLocalTransport$({ + scope, + roomId: "!room:example.org", + useOldestMember$: constant(false), + memberships$: constant(new Epoch([])), + client: { + // eslint-disable-next-line @typescript-eslint/naming-convention + _unstable_getRTCTransports: async () => Promise.resolve([]), + getDomain: () => "", + baseUrl: "example.org", + // These won't be called in this error path but satisfy the type + getOpenIdToken: vi.fn(), + getDeviceId: vi.fn(), + }, + ownMembershipIdentity: ownMemberMock, + forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), + delayId$: constant("delay_id_mock"), + }); + await flushPromises(); + + expect(() => localTransport$.value).toThrow( + new MatrixRTCTransportMissingError(""), + ); + }); + it("throws FailToGetOpenIdToken when OpenID fetch fails", async () => { // Provide a valid config so makeTransportInternal resolves a transport const scope = new ObservableScope(); @@ -65,6 +89,7 @@ describe("LocalTransport", () => { useOldestMember$: constant(false), memberships$: constant(new Epoch([])), client: { + baseUrl: "https://lk.example.org", // Use empty domain to skip .well-known and use config directly getDomain: () => "", // eslint-disable-next-line @typescript-eslint/naming-convention @@ -72,6 +97,9 @@ describe("LocalTransport", () => { getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), }, + ownMembershipIdentity: ownMemberMock, + forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), + delayId$: constant("delay_id_mock"), }); localTransport$.subscribe( (o) => observations.push(o), @@ -86,6 +114,60 @@ describe("LocalTransport", () => { expect(() => localTransport$.value).toThrow(expectedError); }); + it("emits preferred transport after OpenID resolves", async () => { + // Use config so transport discovery succeeds, but delay OpenID JWT fetch + mockConfig({ + livekit: { livekit_service_url: "https://lk.example.org" }, + }); + + const openIdResolver = Promise.withResolvers(); + + vi.spyOn(openIDSFU, "getSFUConfigWithOpenID").mockReturnValue( + openIdResolver.promise, + ); + + const localTransport$ = createLocalTransport$({ + scope, + roomId: "!room:example.org", + useOldestMember$: constant(false), + memberships$: constant(new Epoch([])), + client: { + // eslint-disable-next-line @typescript-eslint/naming-convention + _unstable_getRTCTransports: async () => Promise.resolve([]), + getDomain: () => "", + getOpenIdToken: vi.fn(), + getDeviceId: vi.fn(), + baseUrl: "https://lk.example.org", + }, + ownMembershipIdentity: ownMemberMock, + forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), + delayId$: constant("delay_id_mock"), + }); + + openIdResolver.resolve?.({ + url: "https://lk.example.org", + jwt: "jwt", + livekitAlias: "!room:example.org", + livekitIdentity: ownMemberMock.userId + ":" + ownMemberMock.deviceId, + }); + expect(localTransport$.value).toBe(null); + await flushPromises(); + // final + expect(localTransport$.value).toStrictEqual({ + transport: { + livekit_alias: "!room:example.org", + livekit_service_url: "https://lk.example.org", + type: "livekit", + }, + sfuConfig: { + jwt: "jwt", + livekitAlias: "!room:example.org", + livekitIdentity: "@alice:example.org:DEVICE", + url: "https://lk.example.org", + }, + }); + }); + it("updates local transport when oldest member changes", async () => { // Use config so transport discovery succeeds, but delay OpenID JWT fetch mockConfig({ @@ -109,7 +191,11 @@ describe("LocalTransport", () => { _unstable_getRTCTransports: async () => Promise.resolve([]), getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), + baseUrl: "https://lk.example.org", }, + ownMembershipIdentity: ownMemberMock, + forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), + delayId$: constant("delay_id_mock"), }); openIdResolver.resolve?.(openIdResponse); @@ -117,9 +203,17 @@ describe("LocalTransport", () => { await flushPromises(); // final expect(localTransport$.value).toStrictEqual({ - livekit_alias: "!example_room_id", - livekit_service_url: "https://lk.example.org", - type: "livekit", + transport: { + livekit_alias: "!example_room_id", + livekit_service_url: "https://lk.example.org", + type: "livekit", + }, + sfuConfig: { + jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=", + livekitAlias: "!example_room_id", + livekitIdentity: "@lk_user:ABCDEF", + url: "https://lk.example.org", + }, }); }); @@ -134,11 +228,15 @@ describe("LocalTransport", () => { mockConfig({}); customLivekitUrl.setValue(customLivekitUrl.defaultValue); localTransportOpts = { + ownMembershipIdentity: ownMemberMock, scope, roomId: "!example_room_id", useOldestMember$: constant(false), + forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), + delayId$: constant(null), memberships$: constant(new Epoch([])), client: { + baseUrl: "https://example.org", getDomain: vi.fn().mockReturnValue(""), // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: vi.fn().mockResolvedValue([]), @@ -165,9 +263,17 @@ describe("LocalTransport", () => { expect(localTransport$.value).toBe(null); await flushPromises(); expect(localTransport$.value).toStrictEqual({ - livekit_alias: "!example_room_id", - livekit_service_url: "https://lk.example.org", - type: "livekit", + transport: { + livekit_alias: "!example_room_id", + livekit_service_url: "https://lk.example.org", + type: "livekit", + }, + sfuConfig: { + jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=", + livekitAlias: "!example_room_id", + livekitIdentity: "@lk_user:ABCDEF", + url: "https://lk.example.org", + }, }); }); it("supports getting transport via user settings", async () => { @@ -177,9 +283,17 @@ describe("LocalTransport", () => { expect(localTransport$.value).toBe(null); await flushPromises(); expect(localTransport$.value).toStrictEqual({ - livekit_alias: "!example_room_id", - livekit_service_url: "https://lk.example.org", - type: "livekit", + transport: { + livekit_alias: "!example_room_id", + livekit_service_url: "https://lk.example.org", + type: "livekit", + }, + sfuConfig: { + jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=", + livekitAlias: "!example_room_id", + livekitIdentity: "@lk_user:ABCDEF", + url: "https://lk.example.org", + }, }); }); it("supports getting transport via backend", async () => { @@ -191,9 +305,17 @@ describe("LocalTransport", () => { expect(localTransport$.value).toBe(null); await flushPromises(); expect(localTransport$.value).toStrictEqual({ - livekit_alias: "!example_room_id", - livekit_service_url: "https://lk.example.org", - type: "livekit", + transport: { + livekit_alias: "!example_room_id", + livekit_service_url: "https://lk.example.org", + type: "livekit", + }, + sfuConfig: { + jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=", + livekitAlias: "!example_room_id", + livekitIdentity: "@lk_user:ABCDEF", + url: "https://lk.example.org", + }, }); }); it("fails fast if the openID request fails for backend config", async () => { @@ -222,9 +344,17 @@ describe("LocalTransport", () => { expect(localTransport$.value).toBe(null); await flushPromises(); expect(localTransport$.value).toStrictEqual({ - livekit_alias: "!example_room_id", - livekit_service_url: "https://lk.example.org", - type: "livekit", + transport: { + livekit_alias: "!example_room_id", + livekit_service_url: "https://lk.example.org", + type: "livekit", + }, + sfuConfig: { + jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=", + livekitAlias: "!example_room_id", + livekitIdentity: "@lk_user:ABCDEF", + url: "https://lk.example.org", + }, }); expect(fetchMock.done()).toEqual(true); }); @@ -248,11 +378,15 @@ describe("LocalTransport", () => { it("throws if no options are available", async () => { const localTransport$ = createLocalTransport$({ scope, + ownMembershipIdentity: ownMemberMock, roomId: "!example_room_id", useOldestMember$: constant(false), + forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), + delayId$: constant(null), memberships$: constant(new Epoch([])), client: { getDomain: () => "", + baseUrl: "https://example.org", // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: async () => Promise.resolve([]), // These won't be called in this error path but satisfy the type diff --git a/src/state/CallViewModel/localMember/LocalTransport.ts b/src/state/CallViewModel/localMember/LocalTransport.ts index e72b076f..6e0e56a3 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.ts @@ -23,6 +23,7 @@ import { } from "rxjs"; import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery"; +import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type Behavior } from "../../Behavior.ts"; import { type Epoch, type ObservableScope } from "../../ObservableScope.ts"; @@ -30,9 +31,11 @@ import { Config } from "../../../config/Config.ts"; import { FailToGetOpenIdToken, MatrixRTCTransportMissingError, + NoMatrix2AuthorizationService, } from "../../../utils/errors.ts"; import { getSFUConfigWithOpenID, + type SFUConfig, type OpenIDClientParts, } from "../../../livekit/openIDSFU.ts"; import { areLivekitTransportsEqual } from "../remoteMembers/MatrixLivekitMembers.ts"; @@ -47,11 +50,32 @@ const logger = rootLogger.getChild("[LocalTransport]"); */ interface Props { scope: ObservableScope; + ownMembershipIdentity: CallMembershipIdentityParts; memberships$: Behavior>; - client: Pick & + client: Pick< + MatrixClient, + "getDomain" | "baseUrl" | "_unstable_getRTCTransports" + > & OpenIDClientParts; roomId: string; useOldestMember$: Behavior; + forceJwtEndpoint$: Behavior; + delayId$: Behavior; +} + +export enum JwtEndpointVersion { + Legacy = "legacy", + Matrix_2_0 = "matrix_2_0", +} + +export interface LocalTransportWithSFUConfig { + transport: LivekitTransport; + sfuConfig: SFUConfig; +} +export function isLocalTransportWithSFUConfig( + obj: LivekitTransport | LocalTransportWithSFUConfig, +): obj is LocalTransportWithSFUConfig { + return "transport" in obj && "sfuConfig" in obj; } /** @@ -61,26 +85,53 @@ interface Props { * @prop useOldestMember Whether to use the same transport as the oldest member. * This will only update once the first oldest member appears. Will not recompute if the oldest member leaves. * + * @prop useOldJwtEndpoint$ Whether to set forceOldJwtEndpoint on the returned transport and to use the old JWT endpoint. + * This is used when the connection manager needs to know if it has to use the legacy endpoint which implies a string concatenated rtcBackendIdentity. + * (which is expected for non sticky event based rtc member events) + * @returns The local transport. It will be created using the correct sfu endpoint based on the useOldJwtEndpoint$ value. * @throws MatrixRTCTransportMissingError | FailToGetOpenIdToken */ export const createLocalTransport$ = ({ scope, memberships$, + ownMembershipIdentity, client, roomId, useOldestMember$, -}: Props): Behavior => { + forceJwtEndpoint$, + delayId$, +}: Props): Behavior => { /** * The transport over which we should be actively publishing our media. * undefined when not joined. */ const oldestMemberTransport$ = scope.behavior( - memberships$.pipe( - map( - (memberships) => - memberships.value[0]?.getTransport(memberships.value[0]) ?? null, - ), + combineLatest([memberships$]).pipe( + map(([memberships]) => { + const oldestMember = memberships.value[0]; + const transport = oldestMember?.getTransport(memberships.value[0]); + if (!transport) return null; + return transport; + }), first((t) => t != null && isLivekitTransport(t)), + switchMap((transport) => { + // Get the open jwt token to connect to the sfu + const computeLocalTransportWithSFUConfig = + async (): Promise => { + return { + transport, + sfuConfig: await getSFUConfigWithOpenID( + client, + ownMembershipIdentity, + transport.livekit_service_url, + roomId, + { forceJwtEndpoint: JwtEndpointVersion.Legacy }, + logger, + ), + }; + }; + return from(computeLocalTransportWithSFUConfig()); + }), ), null, ); @@ -91,9 +142,30 @@ export const createLocalTransport$ = ({ * * @throws MatrixRTCTransportMissingError | FailToGetOpenIdToken */ - const preferredTransport$: Behavior = scope.behavior( - customLivekitUrl.value$.pipe( - switchMap((customUrl) => from(makeTransport(client, roomId, customUrl))), + const preferredTransport$ = scope.behavior( + // preferredTransport$ (used for multi sfu) needs to know if we are using the old or new + // jwt endpoint (`get_token` vs `sfu/get`) based on that the jwt endpoint will compute the rtcBackendIdentity + // differently. (sha(`${userId}|${deviceId}|${memberId}`) vs `${userId}|${deviceId}|${memberId}`) + // When using sticky events (we need to use the new endpoint). + combineLatest([customLivekitUrl.value$, delayId$, forceJwtEndpoint$]).pipe( + switchMap(([customUrl, delayId, forceEndpoint]) => { + logger.info( + "Creating preferred transport based on: ", + customUrl, + delayId, + forceEndpoint, + ); + return from( + makeTransport( + client, + ownMembershipIdentity, + roomId, + customUrl, + forceEndpoint, + delayId ?? undefined, + ), + ); + }), ), null, ); @@ -112,7 +184,9 @@ export const createLocalTransport$ = ({ ? (oldestMemberTransport ?? preferredTransport) : preferredTransport, ), - distinctUntilChanged(areLivekitTransportsEqual), + distinctUntilChanged((t1, t2) => + areLivekitTransportsEqual(t1?.transport ?? null, t2?.transport ?? null), + ), ), ); }; @@ -124,25 +198,63 @@ const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci"; * validating auth against the service to ensure it's correct. * Prefers in order: * + * 1. The `urlFromDevSettings` value. If this cannot be validated, the function will throw. * 2. The transports returned via the homeserver. * 3. The transports returned via .well-known. * 4. The transport configured in Element Call's config. * * @param client The authenticated Matrix client for the current user + * @param membership The membership identity of the user. * @param roomId The ID of the room to be connected to. * @param urlFromDevSettings Override URL provided by the user's local config. + * @param forceJwtEndpoint Whether to force a specific JWT endpoint + * - `Legacy` / `Matrix_2_0` + * - `get_token` / `sfu/get` + * - not hashing / hashing the backendIdentity + * @param delayId the delay id passed to the jwt service. + * * @returns A fully validated transport config. * @throws MatrixRTCTransportMissingError | FailToGetOpenIdToken */ async function makeTransport( - client: Pick & + client: Pick< + MatrixClient, + "getDomain" | "baseUrl" | "_unstable_getRTCTransports" + > & OpenIDClientParts, + membership: CallMembershipIdentityParts, roomId: string, urlFromDevSettings: string | null, -): Promise { + forceJwtEndpoint: JwtEndpointVersion, + delayId?: string, +): Promise { logger.trace("Searching for a preferred transport"); + async function doOpenIdAndJWTFromUrl( + url: string, + ): Promise { + const sfuConfig = await getSFUConfigWithOpenID( + client, + membership, + url, + roomId, + { + forceJwtEndpoint: forceJwtEndpoint, + delayEndpointBaseUrl: client.baseUrl, + delayId, + }, + logger, + ); + return { + transport: { + type: "livekit", + livekit_service_url: url, + livekit_alias: sfuConfig.livekitAlias, + }, + sfuConfig, + }; + } // We will call `getSFUConfigWithOpenID` once per transport here as it's our // only mechanism of valiation. This means we will also ask the // homeserver for a OpenID token a few times. Since OpenID tokens are single @@ -153,39 +265,29 @@ async function makeTransport( // DEVTOOL: Highest priority: Load from devtool setting if (urlFromDevSettings !== null) { - logger.info("Using LiveKit transport from dev tools: ", urlFromDevSettings); // Validate that the SFU is up. Otherwise, we want to fail on this // as we don't permit other SFUs. - const config = await getSFUConfigWithOpenID( - client, - urlFromDevSettings, - roomId, - ); - return { - type: "livekit", - livekit_service_url: urlFromDevSettings, - livekit_alias: config.livekitAlias, - }; + // This will call the jwt/sfu/get endpoint to pre create the livekit room. + logger.info("Using LiveKit transport from dev tools: ", urlFromDevSettings); + return await doOpenIdAndJWTFromUrl(urlFromDevSettings); } async function getFirstUsableTransport( transports: Transport[], - ): Promise { + ): Promise { for (const potentialTransport of transports) { if (isLivekitTransportConfig(potentialTransport)) { try { - const { livekitAlias } = await getSFUConfigWithOpenID( - client, + // This will call the jwt/sfu/get endpoint to pre create the livekit room. + return await doOpenIdAndJWTFromUrl( potentialTransport.livekit_service_url, - roomId, ); - return { - ...potentialTransport, - livekit_alias: livekitAlias, - }; } catch (ex) { + // Explictly throw these if (ex instanceof FailToGetOpenIdToken) { - // Explictly throw these + throw ex; + } + if (ex instanceof NoMatrix2AuthorizationService) { throw ex; } logger.debug( @@ -245,18 +347,9 @@ async function makeTransport( const urlFromConf = Config.get().livekit?.livekit_service_url; if (urlFromConf) { try { - const { livekitAlias } = await getSFUConfigWithOpenID( - client, - urlFromConf, - roomId, - ); - const selectedTransport: LivekitTransport = { - type: "livekit", - livekit_service_url: urlFromConf, - livekit_alias: livekitAlias, - }; - logger.info("Using config SFU", selectedTransport); - return selectedTransport; + // This will call the jwt/sfu/get endpoint to pre create the livekit room. + logger.info("Using config SFU", urlFromConf); + return await doOpenIdAndJWTFromUrl(urlFromConf); } catch (ex) { if (ex instanceof FailToGetOpenIdToken) { throw ex; @@ -265,5 +358,6 @@ async function makeTransport( } } + // If we do not have returned a transport by now we throw an error throw new MatrixRTCTransportMissingError(domain ?? ""); } diff --git a/src/state/CallViewModel/remoteMembers/Connection.test.ts b/src/state/CallViewModel/remoteMembers/Connection.test.ts index c1e24eb4..cc430645 100644 --- a/src/state/CallViewModel/remoteMembers/Connection.test.ts +++ b/src/state/CallViewModel/remoteMembers/Connection.test.ts @@ -26,8 +26,8 @@ import fetchMock from "fetch-mock"; import EventEmitter from "events"; import { type IOpenIDToken } from "matrix-js-sdk"; import { logger } from "matrix-js-sdk/lib/logger"; +import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc/LivekitTransport"; -import type { LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { Connection, ConnectionState, @@ -40,7 +40,7 @@ import { FailToGetOpenIdToken, } from "../../../utils/errors.ts"; import { testJWTToken } from "../../../utils/test-fixtures.ts"; -import { mockRemoteParticipant } from "../../../utils/test.ts"; +import { mockRemoteParticipant, ownMemberMock } from "../../../utils/test.ts"; let testScope: ObservableScope; @@ -114,6 +114,7 @@ function setupRemoteConnection(): Connection { client: client, transport: livekitFocus, scope: testScope, + ownMembershipIdentity: ownMemberMock, livekitRoomFactory: () => fakeLivekitRoom, }; @@ -155,6 +156,7 @@ describe("Start connection states", () => { client: client, transport: livekitFocus, scope: testScope, + ownMembershipIdentity: ownMemberMock, livekitRoomFactory: () => fakeLivekitRoom, }; const connection = new Connection(opts, logger); @@ -170,6 +172,7 @@ describe("Start connection states", () => { client: client, transport: livekitFocus, scope: testScope, + ownMembershipIdentity: ownMemberMock, livekitRoomFactory: () => fakeLivekitRoom, }; @@ -220,6 +223,7 @@ describe("Start connection states", () => { client: client, transport: livekitFocus, scope: testScope, + ownMembershipIdentity: ownMemberMock, livekitRoomFactory: () => fakeLivekitRoom, }; @@ -259,7 +263,7 @@ describe("Start connection states", () => { capturedState.cause instanceof Error ) { expect(capturedState.cause.message).toContain( - "SFU Config fetch failed with exception", + "SFU Config fetch failed with status code 500", ); expect(connection.transport.livekit_alias).toEqual( livekitFocus.livekit_alias, @@ -277,6 +281,7 @@ describe("Start connection states", () => { client: client, transport: livekitFocus, scope: testScope, + ownMembershipIdentity: ownMemberMock, livekitRoomFactory: () => fakeLivekitRoom, }; diff --git a/src/state/CallViewModel/remoteMembers/Connection.ts b/src/state/CallViewModel/remoteMembers/Connection.ts index 41dfe665..f286b0cd 100644 --- a/src/state/CallViewModel/remoteMembers/Connection.ts +++ b/src/state/CallViewModel/remoteMembers/Connection.ts @@ -18,6 +18,7 @@ import { import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { BehaviorSubject, map } from "rxjs"; import { type Logger } from "matrix-js-sdk/lib/logger"; +import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { getSFUConfigWithOpenID, @@ -32,8 +33,21 @@ import { SFURoomCreationRestrictedError, UnknownCallError, } from "../../../utils/errors.ts"; +import { type JwtEndpointVersion } from "../localMember/LocalTransport.ts"; export interface ConnectionOpts { + /** + * For the local transport we already do know the jwt token and url. We can reuse it. + * On top the local transport will send additional data to the jwt server to use delayed event delegation. + */ + existingSFUConfig?: SFUConfig; + /** + * For local connections that use the oldest member pattern. here we have not prefetched the sfuConfig + * and hence we need to let the connection do the jwt token fetching. + */ + forceJwtEndpoint?: JwtEndpointVersion; + /** The identity parts to use on this connection */ + ownMembershipIdentity: CallMembershipIdentityParts; /** The media transport to connect to. */ transport: LivekitTransport; /** The Matrix client to use for OpenID and SFU config requests. */ @@ -129,8 +143,10 @@ export class Connection { try { this._state$.next(ConnectionState.FetchingConfig); // We should already have this information after creating the localTransport. - // It would probably be better to forward this here. - const { url, jwt } = await this.getSFUConfigWithOpenID(); + // only call getSFUConfigWithOpenID for connections where we do not have a token yet. (existingJwtTokenData === undefined) + const { url, jwt } = + this.existingSFUConfig ?? + (await this.getSFUConfigForRemoteConnection()); // If we were stopped while fetching the config, don't proceed to connect if (this.stopped) return; @@ -186,11 +202,17 @@ export class Connection { } } - protected async getSFUConfigWithOpenID(): Promise { + protected async getSFUConfigForRemoteConnection(): Promise { + // This will only be called for sfu's where we do not publish ourselves. + // For the local connection we will use the existingJwtTokenData return await getSFUConfigWithOpenID( this.client, + this.ownMembershipIdentity, this.transport.livekit_service_url, this.transport.livekit_alias, + // dont pass any custom opts for the subscribe only connections + {}, + this.logger, ); } @@ -212,7 +234,8 @@ export class Connection { private readonly client: OpenIDClientParts; private readonly logger: Logger; - + private readonly ownMembershipIdentity: CallMembershipIdentityParts; + private readonly existingSFUConfig?: SFUConfig; /** * Creates a new connection to a matrix RTC LiveKit backend. * @@ -221,6 +244,8 @@ export class Connection { * @param logger - The logger to use. */ public constructor(opts: ConnectionOpts, logger: Logger) { + this.ownMembershipIdentity = opts.ownMembershipIdentity; + this.existingSFUConfig = opts.existingSFUConfig; this.logger = logger.getChild("[Connection]"); this.logger.info( `Creating new connection to ${opts.transport.livekit_service_url} ${opts.transport.livekit_alias}`, diff --git a/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts b/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts index c3364059..aa20037c 100644 --- a/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts +++ b/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts @@ -5,7 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import { Room as LivekitRoom, type RoomOptions, @@ -16,10 +15,15 @@ import { import { type Logger } from "matrix-js-sdk/lib/logger"; // imported as inline to support worker when loaded from a cdn (cross domain) import E2EEWorker from "livekit-client/e2ee-worker?worker&inline"; +import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; +import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc/LivekitTransport"; import { type ObservableScope } from "../../ObservableScope.ts"; import { Connection } from "./Connection.ts"; -import type { OpenIDClientParts } from "../../../livekit/openIDSFU.ts"; +import type { + OpenIDClientParts, + SFUConfig, +} from "../../../livekit/openIDSFU.ts"; import type { MediaDevices } from "../../MediaDevices.ts"; import type { Behavior } from "../../Behavior.ts"; import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx"; @@ -28,9 +32,11 @@ import { defaultLiveKitOptions } from "../../../livekit/options.ts"; // TODO evaluate if this should be done like the Publisher Factory export interface ConnectionFactory { createConnection( - transport: LivekitTransport, scope: ObservableScope, + transport: LivekitTransport, + ownMembershipIdentity: CallMembershipIdentityParts, logger: Logger, + sfuConfig?: SFUConfig, ): Connection; } @@ -78,17 +84,30 @@ export class ECConnectionFactory implements ConnectionFactory { this.livekitRoomFactory = livekitRoomFactory ?? defaultFactory; } + /** + * + * @param scope The observable scope (used for clean-up) + * @param transport The transport to use for this connection. + * @param ownMembershipIdentity required to connect (using the jwt service) with the SFU. + * @param logger The logger instance to use for this connection. + * @param sfuConfig optional config in case we already have a token for this connection. + * @returns + */ public createConnection( - transport: LivekitTransport, scope: ObservableScope, + transport: LivekitTransport, + ownMembershipIdentity: CallMembershipIdentityParts, logger: Logger, + sfuConfig?: SFUConfig, ): Connection { return new Connection( { + existingSFUConfig: sfuConfig, transport, client: this.client, scope: scope, livekitRoomFactory: this.livekitRoomFactory, + ownMembershipIdentity, }, logger, ); diff --git a/src/state/CallViewModel/remoteMembers/ConnectionManager.test.ts b/src/state/CallViewModel/remoteMembers/ConnectionManager.test.ts index 280d8ff7..cf930415 100644 --- a/src/state/CallViewModel/remoteMembers/ConnectionManager.test.ts +++ b/src/state/CallViewModel/remoteMembers/ConnectionManager.test.ts @@ -18,9 +18,9 @@ import { } from "./ConnectionManager.ts"; import { type ConnectionFactory } from "./ConnectionFactory.ts"; import { type Connection } from "./Connection.ts"; -import { withTestScheduler } from "../../../utils/test.ts"; +import { ownMemberMock, withTestScheduler } from "../../../utils/test.ts"; import { areLivekitTransportsEqual } from "./MatrixLivekitMembers.ts"; -import { type Behavior } from "../../Behavior.ts"; +import { constant, type Behavior } from "../../Behavior.ts"; // Some test constants @@ -49,7 +49,7 @@ beforeEach(() => { vi.mocked(fakeConnectionFactory).createConnection = vi .fn() .mockImplementation( - (transport: LivekitTransport, scope: ObservableScope) => { + (scope: ObservableScope, transport: LivekitTransport) => { const mockConnection = { transport, remoteParticipants$: new BehaviorSubject([]), @@ -76,10 +76,12 @@ describe("connections$ stream", () => { const { connectionManagerData$ } = createConnectionManager$({ scope: testScope, connectionFactory: fakeConnectionFactory, - inputTransports$: behavior("a", { + localTransport$: constant(null), + remoteTransports$: behavior("a", { a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0), }), logger: logger, + ownMembershipIdentity: ownMemberMock, }); expectObservable( @@ -115,7 +117,8 @@ describe("connections$ stream", () => { const { connectionManagerData$ } = createConnectionManager$({ scope: testScope, connectionFactory: fakeConnectionFactory, - inputTransports$: behavior("abcdef", { + localTransport$: constant(null), + remoteTransports$: behavior("abcdef", { a: new Epoch([TRANSPORT_1], 0), b: new Epoch([TRANSPORT_1], 1), c: new Epoch([TRANSPORT_1], 2), @@ -124,6 +127,7 @@ describe("connections$ stream", () => { f: new Epoch([TRANSPORT_1, TRANSPORT_2], 5), }), logger: logger, + ownMembershipIdentity: ownMemberMock, }); expectObservable( @@ -160,12 +164,14 @@ describe("connections$ stream", () => { const { connectionManagerData$ } = createConnectionManager$({ scope: testScope, connectionFactory: fakeConnectionFactory, - inputTransports$: behavior("abc", { + localTransport$: constant(null), + remoteTransports$: behavior("abc", { a: new Epoch([TRANSPORT_1], 0), b: new Epoch([TRANSPORT_1, TRANSPORT_2], 1), c: new Epoch([TRANSPORT_1], 2), }), logger: logger, + ownMembershipIdentity: ownMemberMock, }); expectObservable( @@ -223,7 +229,7 @@ describe("connectionManagerData$ stream", () => { vi.mocked(fakeConnectionFactory).createConnection = vi .fn() .mockImplementation( - (transport: LivekitTransport, scope: ObservableScope) => { + (scope: ObservableScope, transport: LivekitTransport) => { const fakeRemoteParticipants$ = new BehaviorSubject< RemoteParticipant[] >([]); @@ -275,10 +281,12 @@ describe("connectionManagerData$ stream", () => { const { connectionManagerData$ } = createConnectionManager$({ scope: testScope, connectionFactory: fakeConnectionFactory, - inputTransports$: behavior("a", { + localTransport$: constant(null), + remoteTransports$: behavior("a", { a: new Epoch([TRANSPORT_1, TRANSPORT_2], 0), }), logger, + ownMembershipIdentity: ownMemberMock, }); expectObservable(connectionManagerData$).toBe("abcd", { diff --git a/src/state/CallViewModel/remoteMembers/ConnectionManager.ts b/src/state/CallViewModel/remoteMembers/ConnectionManager.ts index 101e34ed..4295c5f2 100644 --- a/src/state/CallViewModel/remoteMembers/ConnectionManager.ts +++ b/src/state/CallViewModel/remoteMembers/ConnectionManager.ts @@ -7,9 +7,10 @@ Please see LICENSE in the repository root for full details. */ import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; -import { combineLatest, map, of, switchMap, tap } from "rxjs"; +import { combineLatest, map, of, switchMap } from "rxjs"; import { type Logger } from "matrix-js-sdk/lib/logger"; import { type RemoteParticipant } from "livekit-client"; +import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type Behavior } from "../../Behavior.ts"; import { type Connection } from "./Connection.ts"; @@ -17,6 +18,11 @@ import { Epoch, type ObservableScope } from "../../ObservableScope.ts"; import { generateItemsWithEpoch } from "../../../utils/observable.ts"; import { areLivekitTransportsEqual } from "./MatrixLivekitMembers.ts"; import { type ConnectionFactory } from "./ConnectionFactory.ts"; +import { + isLocalTransportWithSFUConfig, + type LocalTransportWithSFUConfig, +} from "../localMember/LocalTransport.ts"; +import { type SFUConfig } from "../../../livekit/openIDSFU.ts"; export class ConnectionManagerData { private readonly store: Map< @@ -65,8 +71,11 @@ export class ConnectionManagerData { interface Props { scope: ObservableScope; connectionFactory: ConnectionFactory; - inputTransports$: Behavior>; + localTransport$: Behavior; + remoteTransports$: Behavior>; + logger: Logger; + ownMembershipIdentity: CallMembershipIdentityParts; } // TODO - write test for scopes (do we really need to bind scope) @@ -79,8 +88,12 @@ export interface IConnectionManager { * @param props - Configuration object * @param props.scope - The observable scope used by this object * @param props.connectionFactory - Used to create new connections - * @param props.inputTransports$ - A list of Behaviors each containing a LIST of LivekitTransport. - * @param props.logger - The logger to use + * @param props.localTransport$ - The local transport to use. (deduplicated with remoteTransports$) + * @param props.remoteTransports$ - All other transports. The connection manager will create connections for each transport. (deduplicated with localTransport$) + * @param props.ownMembershipIdentity - The own membership identity to use. + * @param props.logger - The logger to use. + + * * Each of these behaviors can be interpreted as subscribed list of transports. * * Using `registerTransports` independent external modules can control what connections @@ -93,8 +106,10 @@ export interface IConnectionManager { export function createConnectionManager$({ scope, connectionFactory, - inputTransports$, + localTransport$, + remoteTransports$, logger: parentLogger, + ownMembershipIdentity, }: Props): IConnectionManager { const logger = parentLogger.getChild("[ConnectionManager]"); // TODO logger: only construct one logger from the client and make it compatible via a EC specific sing @@ -107,12 +122,33 @@ export function createConnectionManager$({ * It is build based on the list of subscribed transports (`transportsSubscriptions$`). * externally this is modified via `registerTransports()`. */ - const transports$ = scope.behavior( - inputTransports$.pipe( - map((transports) => transports.mapInner(removeDuplicateTransports)), - tap(({ value: transports }) => { - logger.trace( - `Managing transports: ${transports.map((t) => t.livekit_service_url).join(", ")}`, + const localAndRemoteTransports$: Behavior< + Epoch<(LivekitTransport | LocalTransportWithSFUConfig)[]> + > = scope.behavior( + combineLatest([remoteTransports$, localTransport$]).pipe( + // Combine local and remote transports into one transport array + // and set the forceOldJwtEndpoint property on the local transport + map(([remoteTransports, localTransport]) => { + let localTransportAsArray: LocalTransportWithSFUConfig[] = []; + if (localTransport) { + localTransportAsArray = [localTransport]; + } + const dedupedRemote = removeDuplicateTransports(remoteTransports.value); + const remoteWithoutLocal = dedupedRemote.filter( + (transport) => + !localTransportAsArray.find((l) => + areLivekitTransportsEqual(l.transport, transport), + ), + ); + logger.debug( + "remoteWithoutLocal", + remoteWithoutLocal, + "localTransportAsArray", + localTransportAsArray, + ); + return new Epoch( + [...localTransportAsArray, ...remoteWithoutLocal], + remoteTransports.epoch, ); }), ), @@ -122,25 +158,51 @@ export function createConnectionManager$({ * Connections for each transport in use by one or more session members. */ const connections$ = scope.behavior( - transports$.pipe( + localAndRemoteTransports$.pipe( generateItemsWithEpoch( function* (transports) { - for (const transport of transports) - yield { - keys: [transport.livekit_service_url, transport.livekit_alias], - data: undefined, - }; + for (const transportWithOrWithoutSfuConfig of transports) { + if ( + isLocalTransportWithSFUConfig(transportWithOrWithoutSfuConfig) + ) { + // This is the local transport only the `LocalTransportWithSFUConfig` has a `sfuConfig` field + const { transport, sfuConfig } = transportWithOrWithoutSfuConfig; + yield { + keys: [ + transport.livekit_service_url, + transport.livekit_alias, + sfuConfig, + ], + data: undefined, + }; + } else { + const transport = transportWithOrWithoutSfuConfig; + yield { + keys: [ + transport.livekit_service_url, + transport.livekit_alias, + undefined as undefined | SFUConfig, + ], + data: undefined, + }; + } + } }, - (scope, _data$, serviceUrl, alias) => { - logger.debug(`Creating connection to ${serviceUrl} (${alias})`); + (scope, _data$, serviceUrl, alias, sfuConfig) => { + logger.debug( + `Creating connection to ${serviceUrl} (${alias}, withSfuConfig (local connection?): ${JSON.stringify(sfuConfig) ?? "no config->remote connection"})`, + ); + const connection = connectionFactory.createConnection( + scope, { type: "livekit", livekit_service_url: serviceUrl, livekit_alias: alias, }, - scope, + ownMembershipIdentity, logger, + sfuConfig, ); // Start the connection immediately // Use connection state to track connection progress @@ -190,18 +252,18 @@ export function createConnectionManager$({ ); }), ), - new Epoch(new ConnectionManagerData()), + new Epoch(new ConnectionManagerData(), -1), ); return { connectionManagerData$ }; } -function removeDuplicateTransports( - transports: LivekitTransport[], -): LivekitTransport[] { +function removeDuplicateTransports( + transports: T[], +): T[] { return transports.reduce((acc, transport) => { if (!acc.some((t) => areLivekitTransportsEqual(t, transport))) acc.push(transport); return acc; - }, [] as LivekitTransport[]); + }, [] as T[]); } diff --git a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts index 0c439a6b..b56a17f7 100644 --- a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts +++ b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts @@ -15,7 +15,11 @@ import EventEmitter from "events"; import { ObservableScope } from "../../ObservableScope.ts"; import { ECConnectionFactory } from "./ConnectionFactory.ts"; import type { OpenIDClientParts } from "../../../livekit/openIDSFU.ts"; -import { exampleTransport, mockMediaDevices } from "../../../utils/test.ts"; +import { + exampleTransport, + mockMediaDevices, + ownMemberMock, +} from "../../../utils/test.ts"; import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx"; import { constant } from "../../Behavior"; @@ -72,7 +76,12 @@ describe("ECConnectionFactory - Audio inputs options", () => { echo, noise, ); - ecConnectionFactory.createConnection(exampleTransport, testScope, logger); + ecConnectionFactory.createConnection( + testScope, + exampleTransport, + ownMemberMock, + logger, + ); // Check if Room was constructed with expected options expect(RoomConstructor).toHaveBeenCalledWith( @@ -113,7 +122,12 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => { false, false, ); - ecConnectionFactory.createConnection(exampleTransport, testScope, logger); + ecConnectionFactory.createConnection( + testScope, + exampleTransport, + ownMemberMock, + logger, + ); // Check if Room was constructed with expected options expect(RoomConstructor).toHaveBeenCalledWith( diff --git a/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.test.ts b/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.test.ts index d26bac37..5d34f7be 100644 --- a/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.test.ts +++ b/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.test.ts @@ -10,8 +10,7 @@ import { type CallMembership, type LivekitTransport, } from "matrix-js-sdk/lib/matrixrtc"; -import { getParticipantId } from "matrix-js-sdk/lib/matrixrtc/utils"; -import { combineLatest, map, type Observable } from "rxjs"; +import { BehaviorSubject, combineLatest, map, type Observable } from "rxjs"; import { type IConnectionManager } from "./ConnectionManager.ts"; import { @@ -26,14 +25,18 @@ import { } from "../../ObservableScope.ts"; import { ConnectionManagerData } from "./ConnectionManager.ts"; import { - mockCallMembership, + flushPromises, + mockRtcMembership, mockRemoteParticipant, - withTestScheduler, } from "../../../utils/test.ts"; import { type Connection } from "./Connection.ts"; +import { constant } from "../../Behavior.ts"; let testScope: ObservableScope; +const fallbackMemberId = (userId: string, deviceId: string): string => + `${userId}:${deviceId}`; + const transportA: LivekitTransport = { type: "livekit", livekit_service_url: "https://lk.example.org", @@ -46,16 +49,12 @@ const transportB: LivekitTransport = { livekit_alias: "!alias:sample.com", }; -const bobMembership = mockCallMembership( - "@bob:example.org", - "DEV000", - transportA, -); -const carlMembership = mockCallMembership( - "@carl:sample.com", - "DEV111", - transportB, -); +const bobMembership = mockRtcMembership("@bob:example.org", "DEV000", { + fociPreferred: [transportA], +}); +const carlMembership = mockRtcMembership("@carl:sample.com", "DEV111", { + fociPreferred: [transportB], +}); beforeEach(() => { testScope = new ObservableScope(); @@ -76,52 +75,41 @@ function epochMeWith$( ); } -test("should signal participant not yet connected to livekit", () => { - withTestScheduler(({ behavior, expectObservable }) => { - const { memberships$, membershipsWithTransport$ } = fromMemberships$( - behavior("a", { - a: [bobMembership], - }), - ); +test("should signal participant not yet connected to livekit", async () => { + const mockedMemberships$ = new BehaviorSubject([bobMembership]); + const mockConnectionManagerData$ = new BehaviorSubject( + new ConnectionManagerData(), + ); + const { memberships$, membershipsWithTransport$ } = + createEpochedMemberships$(mockedMemberships$); - const connectionManagerData$ = epochMeWith$( - memberships$, - behavior("a", { - a: new ConnectionManagerData(), - }), - ); + const connectionManagerData$ = epochMeWith$( + memberships$, + mockConnectionManagerData$, + ); - const matrixLivekitMembers$ = createMatrixLivekitMembers$({ - scope: testScope, - membershipsWithTransport$: testScope.behavior(membershipsWithTransport$), - connectionManager: { - connectionManagerData$: connectionManagerData$, - } as unknown as IConnectionManager, - }); - - expectObservable(matrixLivekitMembers$.pipe(map((e) => e.value))).toBe( - "a", - { - a: expect.toSatisfy((data: RemoteMatrixLivekitMember[]) => { - expect(data.length).toEqual(1); - expectObservable(data[0].membership$).toBe("a", { - a: bobMembership, - }); - expectObservable(data[0].participant.value$).toBe("a", { - a: null, - }); - expectObservable(data[0].connection$).toBe("a", { - a: null, - }); - return true; - }), - }, - ); + const matrixLivekitMember$ = createMatrixLivekitMembers$({ + scope: testScope, + membershipsWithTransport$: testScope.behavior(membershipsWithTransport$), + connectionManager: { + connectionManagerData$: connectionManagerData$, + } as unknown as IConnectionManager, }); + + await flushPromises(); + expect(matrixLivekitMember$.value.value).toSatisfy( + (data: RemoteMatrixLivekitMember[]) => { + expect(data.length).toEqual(1); + expect(data[0].membership$.value).toBe(bobMembership); + expect(data[0].participant.value$.value).toBe(null); + expect(data[0].connection$.value).toBe(null); + return true; + }, + ); }); // Helper to create epoch'ed memberships$ and membershipsWithTransport$ from memberships observable. -function fromMemberships$(m$: Observable): { +function createEpochedMemberships$(m$: Observable): { memberships$: Observable>; membershipsWithTransport$: Observable< Epoch<{ membership: CallMembership; transport?: LivekitTransport }[]> @@ -146,32 +134,115 @@ function fromMemberships$(m$: Observable): { }; } -test("should signal participant on a connection that is publishing", () => { - withTestScheduler(({ behavior, expectObservable }) => { - const bobParticipantId = getParticipantId( +test("should signal participant on a connection that is publishing", async () => { + const bobParticipantId = fallbackMemberId( + bobMembership.userId, + bobMembership.deviceId, + ); + + const { memberships$, membershipsWithTransport$ } = createEpochedMemberships$( + constant([bobMembership]), + ); + + const connection = { + transport: bobMembership.getTransport(bobMembership), + } as unknown as Connection; + const dataWithPublisher = new ConnectionManagerData(); + dataWithPublisher.add(connection, [ + mockRemoteParticipant({ identity: bobParticipantId }), + ]); + + const connectionManagerData$ = epochMeWith$( + memberships$, + constant(dataWithPublisher), + ); + + const matrixLivekitMember$ = createMatrixLivekitMembers$({ + scope: testScope, + membershipsWithTransport$: testScope.behavior(membershipsWithTransport$), + connectionManager: { + connectionManagerData$: connectionManagerData$, + } as unknown as IConnectionManager, + }); + + await flushPromises(); + expect(matrixLivekitMember$.value.value).toSatisfy( + (data: RemoteMatrixLivekitMember[]) => { + expect(data.length).toEqual(1); + expect(data[0].membership$.value).toBe(bobMembership); + expect(data[0].participant.value$.value).toSatisfy((participant) => { + expect(participant).toBeDefined(); + expect(participant!.identity).toEqual(bobParticipantId); + return true; + }); + expect(data[0].connection$.value).toBe(connection); + return true; + }, + ); +}); + +test("should signal participant on a connection that is not publishing", async () => { + const { memberships$, membershipsWithTransport$ } = createEpochedMemberships$( + constant([bobMembership]), + ); + + const connection = { + transport: bobMembership.getTransport(bobMembership), + } as unknown as Connection; + const dataWithPublisher = new ConnectionManagerData(); + dataWithPublisher.add(connection, []); + + const connectionManagerData$ = epochMeWith$( + memberships$, + constant(dataWithPublisher), + ); + + const matrixLivekitMember$ = createMatrixLivekitMembers$({ + scope: testScope, + membershipsWithTransport$: testScope.behavior(membershipsWithTransport$), + connectionManager: { + connectionManagerData$: connectionManagerData$, + } as unknown as IConnectionManager, + }); + await flushPromises(); + expect(matrixLivekitMember$.value.value).toSatisfy( + (data: RemoteMatrixLivekitMember[]) => { + expect(data.length).toEqual(1); + expect(data[0].membership$.value).toBe(bobMembership); + expect(data[0].participant.value$.value).toBe(null); + expect(data[0].connection$.value).toBe(connection); + return true; + }, + ); +}); + +describe("Publication edge case", () => { + test("bob is publishing in several connections", async () => { + const { memberships$, membershipsWithTransport$ } = + createEpochedMemberships$(constant([bobMembership, carlMembership])); + + const connectionWithPublisher = new ConnectionManagerData(); + const bobParticipantId = fallbackMemberId( bobMembership.userId, bobMembership.deviceId, ); - - const { memberships$, membershipsWithTransport$ } = fromMemberships$( - behavior("a", { - a: [bobMembership], - }), - ); - - const connection = { - transport: bobMembership.getTransport(bobMembership), + const connectionA = { + transport: transportA, } as unknown as Connection; - const dataWithPublisher = new ConnectionManagerData(); - dataWithPublisher.add(connection, [ + const connectionB = { + transport: transportB, + } as unknown as Connection; + + connectionWithPublisher.add(connectionA, [ + mockRemoteParticipant({ identity: bobParticipantId }), + ]); + connectionWithPublisher.add(connectionB, [ mockRemoteParticipant({ identity: bobParticipantId }), ]); const connectionManagerData$ = epochMeWith$( memberships$, - behavior("a", { - a: dataWithPublisher, - }), + constant(connectionWithPublisher), ); const matrixLivekitMembers$ = createMatrixLivekitMembers$({ @@ -181,213 +252,73 @@ test("should signal participant on a connection that is publishing", () => { connectionManagerData$: connectionManagerData$, } as unknown as IConnectionManager, }); - - expectObservable(matrixLivekitMembers$.pipe(map((e) => e.value))).toBe( - "a", - { - a: expect.toSatisfy((data: RemoteMatrixLivekitMember[]) => { - expect(data.length).toEqual(1); - expectObservable(data[0].membership$).toBe("a", { - a: bobMembership, - }); - expectObservable(data[0].participant.value$).toBe("a", { - a: expect.toSatisfy((participant) => { - expect(participant).toBeDefined(); - expect(participant!.identity).toEqual(bobParticipantId); - return true; - }), - }); - expectObservable(data[0].connection$).toBe("a", { - a: connection, - }); + await flushPromises(); + expect(matrixLivekitMembers$.value.value).toSatisfy( + (data: RemoteMatrixLivekitMember[]) => { + expect(data.length).toEqual(2); + expect(data[0].membership$.value).toBe(bobMembership); + expect(data[0].connection$.value).toBe(connectionA); + expect(data[0].participant.value$.value).toSatisfy((participant) => { + expect(participant).toBeDefined(); + expect(participant!.identity).toEqual(bobParticipantId); return true; - }), + }); + + return true; }, ); }); }); -test("should signal participant on a connection that is not publishing", () => { - withTestScheduler(({ behavior, expectObservable }) => { - const { memberships$, membershipsWithTransport$ } = fromMemberships$( - behavior("a", { - a: [bobMembership], - }), - ); +test("bob is publishing in the wrong connection", async () => { + const mockedMemberships$ = new BehaviorSubject([ + bobMembership, + carlMembership, + ]); - const connection = { - transport: bobMembership.getTransport(bobMembership), - } as unknown as Connection; - const dataWithPublisher = new ConnectionManagerData(); - dataWithPublisher.add(connection, []); + const { memberships$, membershipsWithTransport$ } = + createEpochedMemberships$(mockedMemberships$); - const connectionManagerData$ = epochMeWith$( - memberships$, - behavior("a", { - a: dataWithPublisher, - }), - ); + const connectionWithPublisher = new ConnectionManagerData(); - const matrixLivekitMembers$ = createMatrixLivekitMembers$({ - scope: testScope, - membershipsWithTransport$: testScope.behavior(membershipsWithTransport$), - connectionManager: { - connectionManagerData$: connectionManagerData$, - } as unknown as IConnectionManager, - }); + const bobParticipantId = fallbackMemberId( + bobMembership.userId, + bobMembership.deviceId, + ); + const connectionA = { transport: transportA } as unknown as Connection; + const connectionB = { transport: transportB } as unknown as Connection; - expectObservable(matrixLivekitMembers$.pipe(map((e) => e.value))).toBe( - "a", - { - a: expect.toSatisfy((data: RemoteMatrixLivekitMember[]) => { - expect(data.length).toEqual(1); - expectObservable(data[0].membership$).toBe("a", { - a: bobMembership, - }); - expectObservable(data[0].participant.value$).toBe("a", { - a: null, - }); - expectObservable(data[0].connection$).toBe("a", { - a: connection, - }); - return true; - }), - }, - ); - }); -}); - -describe("Publication edge case", () => { - test("bob is publishing in several connections", () => { - withTestScheduler(({ behavior, expectObservable }) => { - const { memberships$, membershipsWithTransport$ } = fromMemberships$( - behavior("a", { - a: [bobMembership, carlMembership], - }), - ); - - const connectionWithPublisher = new ConnectionManagerData(); - const bobParticipantId = getParticipantId( - bobMembership.userId, - bobMembership.deviceId, - ); - const connectionA = { - transport: transportA, - } as unknown as Connection; - const connectionB = { - transport: transportB, - } as unknown as Connection; - - connectionWithPublisher.add(connectionA, [ - mockRemoteParticipant({ identity: bobParticipantId }), - ]); - connectionWithPublisher.add(connectionB, [ - mockRemoteParticipant({ identity: bobParticipantId }), - ]); - - const connectionManagerData$ = epochMeWith$( - memberships$, - behavior("a", { - a: connectionWithPublisher, - }), - ); - - const matrixLivekitMembers$ = createMatrixLivekitMembers$({ - scope: testScope, - membershipsWithTransport$: testScope.behavior( - membershipsWithTransport$, - ), - connectionManager: { - connectionManagerData$: connectionManagerData$, - } as unknown as IConnectionManager, - }); - - expectObservable(matrixLivekitMembers$.pipe(map((e) => e.value))).toBe( - "a", - { - a: expect.toSatisfy((data: RemoteMatrixLivekitMember[]) => { - expect(data.length).toEqual(2); - expectObservable(data[0].membership$).toBe("a", { - a: bobMembership, - }); - expectObservable(data[0].connection$).toBe("a", { - // The real connection should be from transportA as per the membership - a: connectionA, - }); - expectObservable(data[0].participant.value$).toBe("a", { - a: expect.toSatisfy((participant) => { - expect(participant).toBeDefined(); - expect(participant!.identity).toEqual(bobParticipantId); - return true; - }), - }); - return true; - }), - }, - ); - }); - }); - - test("bob is publishing in the wrong connection", () => { - withTestScheduler(({ behavior, expectObservable }) => { - const { memberships$, membershipsWithTransport$ } = fromMemberships$( - behavior("a", { - a: [bobMembership, carlMembership], - }), - ); - - const connectionWithPublisher = new ConnectionManagerData(); - const bobParticipantId = getParticipantId( - bobMembership.userId, - bobMembership.deviceId, - ); - const connectionA = { transport: transportA } as unknown as Connection; - const connectionB = { transport: transportB } as unknown as Connection; - - // Bob is not publishing on A - connectionWithPublisher.add(connectionA, []); - // Bob is publishing on B but his membership says A - connectionWithPublisher.add(connectionB, [ - mockRemoteParticipant({ identity: bobParticipantId }), - ]); - - const connectionManagerData$ = epochMeWith$( - memberships$, - behavior("a", { - a: connectionWithPublisher, - }), - ); - - const matrixLivekitMembers$ = createMatrixLivekitMembers$({ - scope: testScope, - membershipsWithTransport$: testScope.behavior( - membershipsWithTransport$, - ), - connectionManager: { - connectionManagerData$: connectionManagerData$, - } as unknown as IConnectionManager, - }); - - expectObservable(matrixLivekitMembers$.pipe(map((e) => e.value))).toBe( - "a", - { - a: expect.toSatisfy((data: RemoteMatrixLivekitMember[]) => { - expect(data.length).toEqual(2); - expectObservable(data[0].membership$).toBe("a", { - a: bobMembership, - }); - expectObservable(data[0].connection$).toBe("a", { - // The real connection should be from transportA as per the membership - a: connectionA, - }); - expectObservable(data[0].participant.value$).toBe("a", { - // No participant as Bob is not publishing on his membership transport - a: null, - }); - return true; - }), - }, - ); - }); + // Bob is not publishing on A + connectionWithPublisher.add(connectionA, []); + // Bob is publishing on B but his membership says A + connectionWithPublisher.add(connectionB, [ + mockRemoteParticipant({ identity: bobParticipantId }), + ]); + + const connectionsWithPublisher$ = new BehaviorSubject( + connectionWithPublisher, + ); + const connectionManagerData$ = epochMeWith$( + memberships$, + connectionsWithPublisher$, + ); + + const matrixLivekitMember$ = createMatrixLivekitMembers$({ + scope: testScope, + membershipsWithTransport$: testScope.behavior(membershipsWithTransport$), + connectionManager: { + connectionManagerData$: connectionManagerData$, + } as unknown as IConnectionManager, }); + + await flushPromises(); + expect(matrixLivekitMember$.value.value).toSatisfy( + (data: RemoteMatrixLivekitMember[]) => { + expect(data.length).toEqual(2); + expect(data[0].membership$.value).toBe(bobMembership); + expect(data[0].connection$.value).toBe(connectionA); + expect(data[0].participant.value$.value).toBe(null); + return true; + }, + ); }); diff --git a/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.ts b/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.ts index 6501adb4..10a3e2cb 100644 --- a/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.ts +++ b/src/state/CallViewModel/remoteMembers/MatrixLivekitMembers.ts @@ -84,7 +84,6 @@ export function createMatrixLivekitMembers$({ /** * Stream of all the call members and their associated livekit data (if available). */ - return scope.behavior( combineLatest([ membershipsWithTransport$, @@ -93,47 +92,39 @@ export function createMatrixLivekitMembers$({ filter((values) => values.every((value) => value.epoch === values[0].epoch), ), - map( - ([ - { value: membershipsWithTransports, epoch }, - { value: managerData }, - ]) => - new Epoch([membershipsWithTransports, managerData] as const, epoch), - ), + map(([ms, data]) => new Epoch([ms.value, data.value] as const, ms.epoch)), generateItemsWithEpoch( // Generator function. // creates an array of `{key, data}[]` // Each change in the keys (new key, missing key) will result in a call to the factory function. - function* ([membershipsWithTransports, managerData]) { - for (const { membership, transport } of membershipsWithTransports) { - // TODO! cannot use membership.membershipID yet, Currently its hardcoded by the jwt service to - const participantId = /*membership.membershipID*/ `${membership.userId}:${membership.deviceId}`; - + function* ([membershipsWithTransport, managerData]) { + for (const { membership, transport } of membershipsWithTransport) { const participants = transport ? managerData.getParticipantsForTransport(transport) : []; const participant = - participants.find((p) => p.identity == participantId) ?? null; + participants.find( + (p) => p.identity == membership.rtcBackendIdentity, + ) ?? null; const connection = transport ? managerData.getConnectionForTransport(transport) : null; yield { - keys: [participantId, membership.userId], + keys: [membership.userId, membership.deviceId], data: { membership, participant, connection }, }; } }, // Each update where the key of the generator array do not change will result in updates to the `data$` observable in the factory. - (scope, data$, participantId, userId) => { + (scope, data$, userId, deviceId) => { logger.debug( - `Generating member for participantId: ${participantId}, userId: ${userId}`, + `Generating member for livekitIdentity: ${data$.value.membership.rtcBackendIdentity}, userId:deviceId: ${userId}${deviceId}`, ); const { participant$, ...rest } = scope.splitBehavior(data$); // will only get called once per `participantId, userId` pair. // updates to data$ and as a result to displayName$ and mxcAvatarUrl$ are more frequent. return { - participantId, userId, participant: { type: "remote" as const, value$: participant$ }, ...rest, @@ -141,15 +132,16 @@ export function createMatrixLivekitMembers$({ }, ), ), + new Epoch([], -1), ); } // TODO add back in the callviewmodel pauseWhen(this.pretendToBeDisconnected$) // TODO add this to the JS-SDK -export function areLivekitTransportsEqual( - t1: LivekitTransport | null, - t2: LivekitTransport | null, +export function areLivekitTransportsEqual( + t1: T | null, + t2: T | null, ): boolean { if (t1 && t2) return t1.livekit_service_url === t2.livekit_service_url; // In case we have different lk rooms in the same SFU (depends on the livekit authorization service) diff --git a/src/state/CallViewModel/remoteMembers/MatrixMemberMetadata.test.ts b/src/state/CallViewModel/remoteMembers/MatrixMemberMetadata.test.ts index 6f392351..f7dd775c 100644 --- a/src/state/CallViewModel/remoteMembers/MatrixMemberMetadata.test.ts +++ b/src/state/CallViewModel/remoteMembers/MatrixMemberMetadata.test.ts @@ -18,7 +18,7 @@ import { it } from "vitest"; import { ObservableScope } from "../../ObservableScope.ts"; import type { Room as MatrixRoom } from "matrix-js-sdk/lib/models/room"; import { - mockCallMembership, + mockRtcMembership, mockMatrixRoomMember, withTestScheduler, } from "../../../utils/test.ts"; @@ -111,7 +111,7 @@ describe("MatrixMemberMetadata", () => { rawDisplayName: "it's a me", }); const memberships$ = behavior("a", { - a: [mockCallMembership("@local:example.com", "DEVICE1")], + a: [mockRtcMembership("@local:example.com", "DEVICE1")], }); const metadataStore = createMatrixMemberMetadata$( testScope, @@ -149,8 +149,8 @@ describe("MatrixMemberMetadata", () => { withTestScheduler(({ behavior, expectObservable }) => { const memberships$ = behavior("a", { a: [ - mockCallMembership("@alice:example.com", "DEVICE1"), - mockCallMembership("@bob:example.com", "DEVICE1"), + mockRtcMembership("@alice:example.com", "DEVICE1"), + mockRtcMembership("@bob:example.com", "DEVICE1"), ], }); const metadataStore = createMatrixMemberMetadata$( @@ -179,7 +179,7 @@ describe("MatrixMemberMetadata", () => { setUpBasicRoom(); const memberships$ = behavior("a", { - a: [mockCallMembership("@no-name:foo.bar", "D000")], + a: [mockRtcMembership("@no-name:foo.bar", "D000")], }); const metadataStore = createMatrixMemberMetadata$( testScope, @@ -201,11 +201,11 @@ describe("MatrixMemberMetadata", () => { const memberships$ = behavior("a", { a: [ - mockCallMembership("@bob:example.com", "DEVICE1"), - mockCallMembership("@bob:example.com", "DEVICE2"), - mockCallMembership("@bob:foo.bar", "BOB000"), - mockCallMembership("@carl:example.com", "C000"), - mockCallMembership("@evil:example.com", "E000"), + mockRtcMembership("@bob:example.com", "DEVICE1"), + mockRtcMembership("@bob:example.com", "DEVICE2"), + mockRtcMembership("@bob:foo.bar", "BOB000"), + mockRtcMembership("@carl:example.com", "C000"), + mockRtcMembership("@evil:example.com", "E000"), ], }); @@ -233,10 +233,10 @@ describe("MatrixMemberMetadata", () => { setUpBasicRoom(); const memberships$ = behavior("ab", { - a: [mockCallMembership("@bob:example.com", "DEVICE1")], + a: [mockRtcMembership("@bob:example.com", "DEVICE1")], b: [ - mockCallMembership("@bob:example.com", "DEVICE1"), - mockCallMembership("@bob:foo.bar", "BOB000"), + mockRtcMembership("@bob:example.com", "DEVICE1"), + mockRtcMembership("@bob:foo.bar", "BOB000"), ], }); @@ -262,10 +262,10 @@ describe("MatrixMemberMetadata", () => { const memberships$ = behavior("ab", { a: [ - mockCallMembership("@bob:example.com", "DEVICE1"), - mockCallMembership("@bob:foo.bar", "BOB000"), + mockRtcMembership("@bob:example.com", "DEVICE1"), + mockRtcMembership("@bob:foo.bar", "BOB000"), ], - b: [mockCallMembership("@bob:example.com", "DEVICE1")], + b: [mockRtcMembership("@bob:example.com", "DEVICE1")], }); const metadataStore = createMatrixMemberMetadata$( @@ -292,8 +292,8 @@ describe("MatrixMemberMetadata", () => { const memberships$ = behavior("a", { a: [ - mockCallMembership("@bob:example.com", "B000"), - mockCallMembership("@carl:example.com", "C000"), + mockRtcMembership("@bob:example.com", "B000"), + mockRtcMembership("@carl:example.com", "C000"), ], }); const metadataStore = createMatrixMemberMetadata$( @@ -331,16 +331,16 @@ describe("MatrixMemberMetadata", () => { // - room join/leave // - disambiguate const memberships$ = behavior("ab-d", { - a: [mockCallMembership(CARL, "C000")], + a: [mockRtcMembership(CARL, "C000")], b: [ - mockCallMembership(CARL, "C000"), + mockRtcMembership(CARL, "C000"), // bob joins - mockCallMembership(BOB, "B000"), + mockRtcMembership(BOB, "B000"), ], // c carl gets renamed to BOB d: [ // carl leaves - mockCallMembership(BOB, "B000"), + mockRtcMembership(BOB, "B000"), ], }); schedule("--a-", { @@ -379,8 +379,8 @@ describe("MatrixMemberMetadata", () => { it("should disambiguate users with invisible characters", () => { withTestScheduler(({ behavior, expectObservable }) => { - const bobRtcMember = mockCallMembership("@bob:example.org", "BBBB"); - const bobZeroWidthSpaceRtcMember = mockCallMembership( + const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB"); + const bobZeroWidthSpaceRtcMember = mockRtcMembership( "@bob2:example.org", "BBBB", ); @@ -397,9 +397,9 @@ describe("MatrixMemberMetadata", () => { fakeMemberWith(bobZeroWidthSpace); fakeMemberWith({ userId: "@carol:example.org" }); const memberships$ = behavior("ab", { - a: [mockCallMembership("@carol:example.org", "1111"), bobRtcMember], + a: [mockRtcMembership("@carol:example.org", "1111"), bobRtcMember], b: [ - mockCallMembership("@carol:example.org", "1111"), + mockRtcMembership("@carol:example.org", "1111"), bobRtcMember, bobZeroWidthSpaceRtcMember, ], @@ -450,8 +450,8 @@ describe("MatrixMemberMetadata", () => { it("should strip RTL characters from displayname", () => { withTestScheduler(({ behavior, expectObservable }) => { - const daveRtcMember = mockCallMembership("@dave:example.org", "DDDD"); - const daveRTLRtcMember = mockCallMembership( + const daveRtcMember = mockRtcMembership("@dave:example.org", "DDDD"); + const daveRTLRtcMember = mockRtcMembership( "@dave2:example.org", "DDDD", ); @@ -466,9 +466,9 @@ describe("MatrixMemberMetadata", () => { fakeMemberWith(daveRTL); fakeMemberWith(dave); const memberships$ = behavior("ab", { - a: [mockCallMembership("@carol:example.org", "DDDD")], + a: [mockRtcMembership("@carol:example.org", "DDDD")], b: [ - mockCallMembership("@carol:example.org", "DDDD"), + mockRtcMembership("@carol:example.org", "DDDD"), daveRtcMember, daveRTLRtcMember, ], @@ -527,8 +527,8 @@ describe("MatrixMemberMetadata", () => { }); const memberships$ = behavior("a", { a: [ - mockCallMembership("@local:example.com", "DEVICE1"), - mockCallMembership("@alice:example.com", "DEVICE1"), + mockRtcMembership("@local:example.com", "DEVICE1"), + mockRtcMembership("@alice:example.com", "DEVICE1"), ], }); const metadataStore = createMatrixMemberMetadata$( @@ -562,12 +562,12 @@ describe("MatrixMemberMetadata", () => { fakeMemberWith({ userId: "@carl:example.com" }); fakeMemberWith({ userId: "@bob:example.com" }); const memberships$ = behavior("ab-d", { - a: [mockCallMembership("@bob:example.com", "B000")], + a: [mockRtcMembership("@bob:example.com", "B000")], b: [ - mockCallMembership("@bob:example.com", "B000"), - mockCallMembership("@carl:example.com", "C000"), + mockRtcMembership("@bob:example.com", "B000"), + mockRtcMembership("@carl:example.com", "C000"), ], - d: [mockCallMembership("@carl:example.com", "C000")], + d: [mockRtcMembership("@carl:example.com", "C000")], }); const metadataStore = createMatrixMemberMetadata$( diff --git a/src/state/CallViewModel/remoteMembers/integration.test.ts b/src/state/CallViewModel/remoteMembers/integration.test.ts index d885ddc6..c29f07c0 100644 --- a/src/state/CallViewModel/remoteMembers/integration.test.ts +++ b/src/state/CallViewModel/remoteMembers/integration.test.ts @@ -21,8 +21,9 @@ import { import { ECConnectionFactory } from "./ConnectionFactory.ts"; import { type OpenIDClientParts } from "../../../livekit/openIDSFU.ts"; import { - mockCallMembership, mockMediaDevices, + mockRtcMembership, + ownMemberMock, withTestScheduler, } from "../../../utils/test.ts"; import { type ProcessorState } from "../../../livekit/TrackProcessorContext.tsx"; @@ -33,6 +34,7 @@ import { } from "./MatrixLivekitMembers.ts"; import { createConnectionManager$ } from "./ConnectionManager.ts"; import { membershipsAndTransports$ } from "../../SessionBehaviors.ts"; +import { constant } from "../../Behavior.ts"; import { testJWTToken } from "../../../utils/test-fixtures.ts"; // Test the integration of ConnectionManager and MatrixLivekitMerger @@ -99,9 +101,9 @@ afterEach(() => { test("bob, carl, then bob joining no tracks yet", () => { withTestScheduler(({ expectObservable, behavior, scope }) => { - const bobMembership = mockCallMembership("@bob:example.com", "BDEV000"); - const carlMembership = mockCallMembership("@carl:example.com", "CDEV000"); - const daveMembership = mockCallMembership("@dave:foo.bar", "DDEV000"); + const bobMembership = mockRtcMembership("@bob:example.com", "BDEV000"); + const carlMembership = mockRtcMembership("@carl:example.com", "CDEV000"); + const daveMembership = mockRtcMembership("@dave:foo.bar", "DDEV000"); const eMarble = "abc"; const vMarble = "abc"; @@ -121,8 +123,10 @@ test("bob, carl, then bob joining no tracks yet", () => { const connectionManager = createConnectionManager$({ scope: testScope, connectionFactory: ecConnectionFactory, - inputTransports$: membershipsAndTransports.transports$, + localTransport$: constant(null), + remoteTransports$: membershipsAndTransports.transports$, logger: logger, + ownMembershipIdentity: ownMemberMock, }); const matrixLivekitMembers$ = createMatrixLivekitMembers$({ diff --git a/src/state/CallViewModelWidget.test.ts b/src/state/CallViewModelWidget.test.ts index 5d6442f1..76776720 100644 --- a/src/state/CallViewModelWidget.test.ts +++ b/src/state/CallViewModelWidget.test.ts @@ -37,7 +37,7 @@ vi.mock("../widget", () => ({ it.each([ [MatrixRTCMode.Legacy], - [MatrixRTCMode.Compatibil], + [MatrixRTCMode.Compatibility], [MatrixRTCMode.Matrix_2_0], ])( "expect leave when ElementWidgetActions.HangupCall is called (%s mode)", diff --git a/src/state/MediaDevices.ts b/src/state/MediaDevices.ts index 2349e361..cea97519 100644 --- a/src/state/MediaDevices.ts +++ b/src/state/MediaDevices.ts @@ -212,9 +212,10 @@ class AudioInput implements MediaDevice { } } -class AudioOutput - implements MediaDevice -{ +class AudioOutput implements MediaDevice< + AudioOutputDeviceLabel, + SelectedAudioOutputDevice +> { private logger = rootLogger.getChild("[MediaDevices AudioOutput]"); public readonly available$ = this.scope.behavior( availableRawDevices$( @@ -274,9 +275,10 @@ class AudioOutput } } -class ControlledAudioOutput - implements MediaDevice -{ +class ControlledAudioOutput implements MediaDevice< + AudioOutputDeviceLabel, + SelectedAudioOutputDevice +> { private logger = rootLogger.getChild("[MediaDevices ControlledAudioOutput]"); // We need to subscribe to the raw devices so that the OS does update the input // back to what it was before. otherwise we will switch back to the default diff --git a/src/state/MediaViewModel.ts b/src/state/MediaViewModel.ts index 9888d6bf..3da69c46 100644 --- a/src/state/MediaViewModel.ts +++ b/src/state/MediaViewModel.ts @@ -257,6 +257,7 @@ abstract class BaseMediaViewModel { * The Matrix user to which this media belongs. */ public readonly userId: string, + public readonly rtcBackendIdentity: string, // We don't necessarily have a participant if a user connects via MatrixRTC but not (yet) through // livekit. protected readonly participant$: Observable< @@ -406,6 +407,7 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel { scope: ObservableScope, id: string, userId: string, + rtcBackendIdentity: string, participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom$: Behavior, @@ -419,6 +421,7 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel { scope, id, userId, + rtcBackendIdentity, participant$, encryptionSystem, Track.Source.Microphone, @@ -544,6 +547,7 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel { scope: ObservableScope, id: string, userId: string, + rtcBackendIdentity: string, participant$: Behavior, encryptionSystem: EncryptionSystem, livekitRoom$: Behavior, @@ -558,6 +562,7 @@ export class LocalUserMediaViewModel extends BaseUserMediaViewModel { scope, id, userId, + rtcBackendIdentity, participant$, encryptionSystem, livekitRoom$, @@ -671,6 +676,7 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel { scope: ObservableScope, id: string, userId: string, + rtcBackendIdentity: string, participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom$: Behavior, @@ -685,6 +691,7 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel { scope, id, userId, + rtcBackendIdentity, participant$, encryptionSystem, livekitRoom$, @@ -772,6 +779,7 @@ export class ScreenShareViewModel extends BaseMediaViewModel { scope: ObservableScope, id: string, userId: string, + rtcBackendIdentity: string, participant$: Observable, encryptionSystem: EncryptionSystem, livekitRoom$: Behavior, @@ -785,6 +793,7 @@ export class ScreenShareViewModel extends BaseMediaViewModel { scope, id, userId, + rtcBackendIdentity, participant$, encryptionSystem, Track.Source.ScreenShareAudio, diff --git a/src/state/MuteStates.test.ts b/src/state/MuteStates.test.ts index f2a6e35f..d62074c3 100644 --- a/src/state/MuteStates.test.ts +++ b/src/state/MuteStates.test.ts @@ -1,5 +1,5 @@ /* -Copyright 2025 Element Creations Ltd. +Copyright 2025-2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. @@ -48,13 +48,7 @@ describe("MuteState", () => { select(): void {}, } as unknown as MediaDevice; - const muteState = new MuteState( - testScope, - deviceStub, - constant(true), - true, - forceMute$, - ); + const muteState = new MuteState(testScope, deviceStub, true, forceMute$); let lastEnabled: boolean = false; muteState.enabled$.subscribe((enabled) => { lastEnabled = enabled; @@ -163,12 +157,10 @@ describe("MuteStates", () => { videoInput: aVideoInput(), // other devices are not relevant for this test }); - const muteStates = new MuteStates( - testScope, - mediaDevices, - // consider joined - constant(true), - ); + const muteStates = new MuteStates(testScope, mediaDevices, { + audioEnabled: false, + videoEnabled: false, + }); let latestSyncedState: boolean | null = null; muteStates.video.setHandler(async (enabled: boolean): Promise => { diff --git a/src/state/MuteStates.ts b/src/state/MuteStates.ts index 632e0426..e45f195a 100644 --- a/src/state/MuteStates.ts +++ b/src/state/MuteStates.ts @@ -1,5 +1,6 @@ /* Copyright 2023-2025 New Vector Ltd. +Copyright 2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. @@ -24,8 +25,6 @@ import { import { type MediaDevices, type MediaDevice } from "../state/MediaDevices"; import { ElementWidgetActions, widget } from "../widget"; -import { Config } from "../config/Config"; -import { getUrlParams } from "../UrlParams"; import { type ObservableScope } from "./ObservableScope"; import { type Behavior, constant } from "./Behavior"; @@ -43,12 +42,6 @@ const defaultHandler: Handler = async (desired) => Promise.resolve(desired); * Do not use directly outside of tests. */ export class MuteState { - // TODO: rewrite this to explain behavior, it is not understandable, and cannot add logging - private readonly enabledByDefault$ = - this.enabledByConfig && !getUrlParams().skipLobby - ? this.joined$.pipe(map((isJoined) => !isJoined)) - : of(false); - private readonly handler$ = new BehaviorSubject(defaultHandler); public setHandler(handler: Handler): void { @@ -73,76 +66,73 @@ export class MuteState { private readonly data$ = this.scope.behavior( this.canControlDevices$.pipe( distinctUntilChanged(), - withLatestFrom( - this.enabledByDefault$, - (canControlDevices, enabledByDefault) => { + map((canControlDevices) => { + logger.info( + `MuteState: canControlDevices: ${canControlDevices}, enabled by default: ${this.enabledByDefault}`, + ); + if (!canControlDevices) { logger.info( - `MuteState: canControlDevices: ${canControlDevices}, enabled by default: ${enabledByDefault}`, + `MuteState: devices connected: ${canControlDevices}, disabling`, ); - if (!canControlDevices) { - logger.info( - `MuteState: devices connected: ${canControlDevices}, disabling`, - ); - // We need to sync the mute state with the handler - // to ensure nothing is beeing published. - this.handler$.value(false).catch((err) => { - logger.error("MuteState-disable: handler error", err); - }); - return { enabled$: of(false), set: null, toggle: null }; - } + // We need to sync the mute state with the handler + // to ensure nothing is beeing published. + this.handler$.value(false).catch((err) => { + logger.error("MuteState-disable: handler error", err); + }); + return { enabled$: of(false), set: null, toggle: null }; + } - // Assume the default value only once devices are actually connected - let enabled = enabledByDefault; - const set$ = new Subject(); - const toggle$ = new Subject(); - const desired$ = merge(set$, toggle$.pipe(map(() => !enabled))); - const enabled$ = new Observable((subscriber) => { - subscriber.next(enabled); - let latestDesired = enabledByDefault; - let syncing = false; + // Assume the default value only once devices are actually connected + let enabled = this.enabledByDefault; + const set$ = new Subject(); + const toggle$ = new Subject(); + const desired$ = merge(set$, toggle$.pipe(map(() => !enabled))); + const enabled$ = new Observable((subscriber) => { + subscriber.next(enabled); + let latestDesired = this.enabledByDefault; + let syncing = false; - const sync = async (): Promise => { - if (enabled === latestDesired) syncing = false; - else { - const previouslyEnabled = enabled; - enabled = await firstValueFrom( - this.handler$.pipe( - switchMap(async (handler) => handler(latestDesired)), - ), - ); - if (enabled === previouslyEnabled) { - syncing = false; - } else { - subscriber.next(enabled); - syncing = true; - sync().catch((err) => { - // TODO: better error handling - logger.error("MuteState: handler error", err); - }); - } - } - }; - - const s = desired$.subscribe((desired) => { - latestDesired = desired; - if (syncing === false) { + const sync = async (): Promise => { + if (enabled === latestDesired) syncing = false; + else { + const previouslyEnabled = enabled; + enabled = await firstValueFrom( + this.handler$.pipe( + switchMap(async (handler) => handler(latestDesired)), + ), + ); + if (enabled === previouslyEnabled) { + syncing = false; + } else { + subscriber.next(enabled); syncing = true; sync().catch((err) => { // TODO: better error handling logger.error("MuteState: handler error", err); }); } - }); - return (): void => s.unsubscribe(); - }); - - return { - set: (enabled: boolean): void => set$.next(enabled), - toggle: (): void => toggle$.next(), - enabled$, + } }; - }, - ), + + const s = desired$.subscribe((desired) => { + latestDesired = desired; + if (syncing === false) { + syncing = true; + sync().catch((err) => { + // TODO: better error handling + logger.error("MuteState: handler error", err); + }); + } + }); + return (): void => s.unsubscribe(); + }); + + return { + set: (enabled: boolean): void => set$.next(enabled), + toggle: (): void => toggle$.next(), + enabled$, + }; + }), ), ); @@ -160,8 +150,7 @@ export class MuteState { public constructor( private readonly scope: ObservableScope, private readonly device: MediaDevice, - private readonly joined$: Observable, - private readonly enabledByConfig: boolean, + private readonly enabledByDefault: boolean, /** * An optional observable which, when it emits `true`, will force the mute. * Used for video to stop camera when earpiece mode is on. @@ -176,10 +165,10 @@ export class MuteStates { * True if the selected audio output device is an earpiece. * Used to force-disable video when on earpiece. */ - private readonly isEarpiece$ = combineLatest( + private readonly isEarpiece$ = combineLatest([ this.mediaDevices.audioOutput.available$, this.mediaDevices.audioOutput.selected$, - ).pipe( + ]).pipe( map(([available, selected]) => { if (!selected?.id) return false; const device = available.get(selected.id); @@ -191,22 +180,23 @@ export class MuteStates { public readonly audio = new MuteState( this.scope, this.mediaDevices.audioInput, - this.joined$, - Config.get().media_devices.enable_audio, + this.initialMuteState.audioEnabled, constant(false), ); public readonly video = new MuteState( this.scope, this.mediaDevices.videoInput, - this.joined$, - Config.get().media_devices.enable_video, + this.initialMuteState.videoEnabled, this.isEarpiece$, ); public constructor( private readonly scope: ObservableScope, private readonly mediaDevices: MediaDevices, - private readonly joined$: Observable, + private readonly initialMuteState: { + audioEnabled: boolean; + videoEnabled: boolean; + }, ) { if (widget !== null) { // Sync our mute states with the hosting client diff --git a/src/state/ScreenShare.ts b/src/state/ScreenShare.ts index 0a241cdf..e4f5de1f 100644 --- a/src/state/ScreenShare.ts +++ b/src/state/ScreenShare.ts @@ -28,6 +28,7 @@ export class ScreenShare { private readonly scope: ObservableScope, id: string, userId: string, + rtcBackendIdentity: string, participant: LocalParticipant | RemoteParticipant, encryptionSystem: EncryptionSystem, livekitRoom$: Behavior, @@ -40,6 +41,7 @@ export class ScreenShare { this.scope, id, userId, + rtcBackendIdentity, of(participant), encryptionSystem, livekitRoom$, diff --git a/src/state/UserMedia.ts b/src/state/UserMedia.ts index 690870e6..2a125257 100644 --- a/src/state/UserMedia.ts +++ b/src/state/UserMedia.ts @@ -75,6 +75,7 @@ export class UserMedia { this.scope, this.id, this.userId, + this.rtcBackendIdentity, this.participant.value$, this.encryptionSystem, this.livekitRoom$, @@ -89,6 +90,7 @@ export class UserMedia { this.scope, this.id, this.userId, + this.rtcBackendIdentity, this.participant.value$, this.encryptionSystem, this.livekitRoom$, @@ -140,6 +142,7 @@ export class UserMedia { scope, `${this.id}:${key}`, this.userId, + this.rtcBackendIdentity, p, this.encryptionSystem, this.livekitRoom$, @@ -191,6 +194,7 @@ export class UserMedia { private readonly scope: ObservableScope, public readonly id: string, private readonly userId: string, + private readonly rtcBackendIdentity: string, private readonly participant: TaggedParticipant, private readonly encryptionSystem: EncryptionSystem, private readonly livekitRoom$: Behavior, diff --git a/src/state/initialMuteState.test.ts b/src/state/initialMuteState.test.ts new file mode 100644 index 00000000..abbb52cd --- /dev/null +++ b/src/state/initialMuteState.test.ts @@ -0,0 +1,72 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { test, expect } from "vitest"; +import { type RTCCallIntent } from "matrix-js-sdk/lib/matrixrtc"; + +import { calculateInitialMuteState } from "./initialMuteState"; + +test.each<{ + callIntent: RTCCallIntent; + isWidgetMode: boolean; +}>([ + { callIntent: "audio", isWidgetMode: false }, + { callIntent: "audio", isWidgetMode: true }, + { callIntent: "video", isWidgetMode: false }, + { callIntent: "video", isWidgetMode: true }, + { callIntent: "unknown", isWidgetMode: false }, + { callIntent: "unknown", isWidgetMode: true }, +])( + "Should allow to unmute on start if not skipping lobby (callIntent: $callIntent, packageType: $packageType)", + ({ callIntent, isWidgetMode }) => { + const { audioEnabled, videoEnabled } = calculateInitialMuteState( + false, + callIntent, + isWidgetMode, + ); + expect(audioEnabled).toBe(true); + expect(videoEnabled).toBe(callIntent !== "audio"); + }, +); + +test.each<{ + callIntent: RTCCallIntent; +}>([ + { callIntent: "audio" }, + { callIntent: "video" }, + { callIntent: "unknown" }, +])( + "Should always mute on start if skipping lobby on non widget mode (callIntent: $callIntent)", + ({ callIntent }) => { + const { audioEnabled, videoEnabled } = calculateInitialMuteState( + true, + callIntent, + false, + ); + expect(audioEnabled).toBe(false); + expect(videoEnabled).toBe(false); + }, +); + +test.each<{ + callIntent: RTCCallIntent; +}>([ + { callIntent: "audio" }, + { callIntent: "video" }, + { callIntent: "unknown" }, +])( + "Can start unmuted if skipping lobby on widget mode (callIntent: $callIntent)", + ({ callIntent }) => { + const { audioEnabled, videoEnabled } = calculateInitialMuteState( + true, + callIntent, + true, + ); + expect(audioEnabled).toBe(true); + expect(videoEnabled).toBe(callIntent !== "audio"); + }, +); diff --git a/src/state/initialMuteState.ts b/src/state/initialMuteState.ts new file mode 100644 index 00000000..4d27cdda --- /dev/null +++ b/src/state/initialMuteState.ts @@ -0,0 +1,42 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { logger } from "matrix-js-sdk/lib/logger"; +import { type RTCCallIntent } from "matrix-js-sdk/lib/matrixrtc"; + +/** + * Calculates the initial mute state for media devices based on configuration. + * + * It is not always possible to start the widget with audio/video unmuted due to privacy concerns. + * This function encapsulates the logic to determine the appropriate initial state. + */ +export function calculateInitialMuteState( + skipLobby: boolean, + callIntent: RTCCallIntent | undefined, + isWidgetMode: boolean, +): { audioEnabled: boolean; videoEnabled: boolean } { + logger.debug( + `calculateInitialMuteState: skipLobby=${skipLobby}, callIntent=${callIntent} isWidgetMode=${isWidgetMode}`, + ); + + if (skipLobby && !isWidgetMode) { + // If not in widget mode and lobby is skipped, default to muted to protect user privacy. + // In the SPA context we don't want to unmute users without giving them a chance to adjust their settings first. + return { + audioEnabled: false, + videoEnabled: false, + }; + } + + // Embedded contexts are trusted environments, so they allow unmuted by default. + // Same for when showing a lobby, as users can adjust their settings there. + // Additionally, if the call intent is "audio", we disable video by default. + return { + audioEnabled: true, + videoEnabled: callIntent != "audio", + }; +} diff --git a/src/tile/GridTile.tsx b/src/tile/GridTile.tsx index 2f750c50..92262f05 100644 --- a/src/tile/GridTile.tsx +++ b/src/tile/GridTile.tsx @@ -113,6 +113,7 @@ const UserMediaTile: FC = ({ }, [vm], ); + const rtcBackendIdentity = vm.rtcBackendIdentity; const handRaised = useBehavior(vm.handRaised$); const reaction = useBehavior(vm.reaction$); @@ -200,6 +201,7 @@ const UserMediaTile: FC = ({ focusUrl={focusUrl} audioStreamStats={audioStreamStats} videoStreamStats={videoStreamStats} + rtcBackendIdentity={rtcBackendIdentity} {...props} /> ); diff --git a/src/tile/MediaView.tsx b/src/tile/MediaView.tsx index 8bb38d94..fadc9d2b 100644 --- a/src/tile/MediaView.tsx +++ b/src/tile/MediaView.tsx @@ -18,7 +18,11 @@ import styles from "./MediaView.module.css"; import { Avatar } from "../Avatar"; import { type EncryptionStatus } from "../state/MediaViewModel"; import { RaisedHandIndicator } from "../reactions/RaisedHandIndicator"; -import { showHandRaisedTimer, useSetting } from "../settings/settings"; +import { + showConnectionStats, + showHandRaisedTimer, + useSetting, +} from "../settings/settings"; import { type ReactionOption } from "../reactions"; import { ReactionIndicator } from "../reactions/ReactionIndicator"; import { RTCConnectionStats } from "../RTCConnectionStats"; @@ -46,6 +50,7 @@ interface Props extends ComponentProps { waitingForMedia?: boolean; audioStreamStats?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats; videoStreamStats?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats; + rtcBackendIdentity?: string; // The focus url, mainly for debugging purposes focusUrl?: string; } @@ -74,11 +79,13 @@ export const MediaView: FC = ({ waitingForMedia, audioStreamStats, videoStreamStats, + rtcBackendIdentity, focusUrl, ...props }) => { const { t } = useTranslation(); const [handRaiseTimerVisible] = useSetting(showHandRaisedTimer); + const [showConnectioStats] = useSetting(showConnectionStats); const avatarSize = Math.round(Math.min(targetWidth, targetHeight) / 2); @@ -132,14 +139,18 @@ export const MediaView: FC = ({ {waitingForMedia && (
{t("video_tile.waiting_for_media")} + {showConnectioStats ? " " + rtcBackendIdentity : ""}
)} {(audioStreamStats || videoStreamStats) && ( - + <> + + )} {/* TODO: Bring this back once encryption status is less broken */} {/*encryptionStatus !== EncryptionStatus.Okay && ( diff --git a/src/tile/SpotlightTile.tsx b/src/tile/SpotlightTile.tsx index 8109784f..2042e819 100644 --- a/src/tile/SpotlightTile.tsx +++ b/src/tile/SpotlightTile.tsx @@ -70,8 +70,7 @@ interface SpotlightUserMediaItemBaseProps extends SpotlightItemBaseProps { videoFit: "contain" | "cover"; } -interface SpotlightLocalUserMediaItemProps - extends SpotlightUserMediaItemBaseProps { +interface SpotlightLocalUserMediaItemProps extends SpotlightUserMediaItemBaseProps { vm: LocalUserMediaViewModel; } @@ -85,8 +84,7 @@ const SpotlightLocalUserMediaItem: FC = ({ SpotlightLocalUserMediaItem.displayName = "SpotlightLocalUserMediaItem"; -interface SpotlightRemoteUserMediaItemProps - extends SpotlightUserMediaItemBaseProps { +interface SpotlightRemoteUserMediaItemProps extends SpotlightUserMediaItemBaseProps { vm: RemoteUserMediaViewModel; } diff --git a/src/utils/errors.ts b/src/utils/errors.ts index cddf90de..20a282e6 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -19,6 +19,7 @@ export enum ErrorCode { INSUFFICIENT_CAPACITY_ERROR = "INSUFFICIENT_CAPACITY_ERROR", E2EE_NOT_SUPPORTED = "E2EE_NOT_SUPPORTED", OPEN_ID_ERROR = "OPEN_ID_ERROR", + NO_MATRIX_2_AUTHORIZATION_SERVICE = "NO_MATRIX_2_0_AUTHORIZATION_SERVICE", SFU_ERROR = "SFU_ERROR", UNKNOWN_ERROR = "UNKNOWN_ERROR", } @@ -171,6 +172,23 @@ export class FailToGetOpenIdToken extends ElementCallError { } } +export class NoMatrix2AuthorizationService extends ElementCallError { + /** + * Creates an instance of NoMatrix2_0AuthorizationService. + * @param error - The underlying error that caused the failure. + */ + public constructor(error: Error) { + super( + t("error.generic"), + ErrorCode.NO_MATRIX_2_AUTHORIZATION_SERVICE, + ErrorCategory.CONFIGURATION_ISSUE, + t("error.no_matrix_2_authorization_service"), + // Properly set it as a cause for a better reporting on sentry + error, + ); + } +} + /** * Error indicating a failure to start publishing on a LiveKit connection. */ diff --git a/src/utils/test-fixtures.ts b/src/utils/test-fixtures.ts index f915bb19..dcdb9f9c 100644 --- a/src/utils/test-fixtures.ts +++ b/src/utils/test-fixtures.ts @@ -17,14 +17,16 @@ export const localRtcMemberDevice2 = mockRtcMembership( "2222", ); export const local = mockMatrixRoomMember(localRtcMember); -// export const localParticipant = mockLocalParticipant({ identity: "" }); + export const localId = `${local.userId}:${localRtcMember.deviceId}`; -export const aliceRtcMember = mockRtcMembership("@alice:example.org", "AAAA"); +export const aliceDeviceId = "AAAA"; +export const aliceUserId = "@alice:example.org"; +export const aliceId = `${aliceUserId}:${aliceDeviceId}`; +export const aliceRtcMember = mockRtcMembership(aliceUserId, aliceDeviceId); export const alice = mockMatrixRoomMember(aliceRtcMember, { rawDisplayName: "Alice", }); -export const aliceId = `${alice.userId}:${aliceRtcMember.deviceId}`; export const aliceParticipant = mockRemoteParticipant({ identity: aliceId }); export const aliceDoppelgangerRtcMember = mockRtcMembership( @@ -38,11 +40,13 @@ export const aliceDoppelganger = mockMatrixRoomMember( }, ); -export const bobRtcMember = mockRtcMembership("@bob:example.org", "BBBB"); +export const bobDeviceId = "BBBB"; +export const bobUserId = "@bob:example.org"; +export const bobId = `${bobUserId}:${bobDeviceId}`; +export const bobRtcMember = mockRtcMembership(bobUserId, bobDeviceId); export const bob = mockMatrixRoomMember(bobRtcMember, { rawDisplayName: "Bob", }); -export const bobId = `${bob.userId}:${bobRtcMember.deviceId}`; export const bobZeroWidthSpaceRtcMember = mockRtcMembership( "@bob2:example.org", diff --git a/src/utils/test.ts b/src/utils/test.ts index 9a845908..c99eb77d 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -50,6 +50,7 @@ import { type KeyTransportEvents, type KeyTransportEventsHandlerMap, } from "matrix-js-sdk/lib/matrixrtc/IKeyTransport"; +import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { LocalUserMediaViewModel, @@ -201,40 +202,30 @@ export const exampleTransport: LivekitTransport = { livekit_alias: "!alias:example.org", }; -export function mockCallMembership( - userId: string, - deviceId: string, - transport?: Transport, -): CallMembership { - const t = transport ?? transportForUser(userId); - return { - userId: userId, - deviceId: deviceId, - getTransport: vi.fn().mockReturnValue(t), - transports: [t], - } as unknown as CallMembership; -} - -function transportForUser(userId: string): Transport { - const domain = userId.split(":")[1]; - return { - type: "livekit", - livekit_service_url: `https://lk.${domain}`, - livekit_alias: `!alias:${domain}`, - }; -} - export function mockRtcMembership( user: string | RoomMember, deviceId: string, - callId = "", - fociPreferred: Transport[] = [exampleTransport], - focusActive: LivekitFocusSelection = { - type: "livekit", - focus_selection: "oldest_membership", + customOverwrites?: { + rtcBackendIdentity?: string; + callId?: string; + fociPreferred?: Transport[]; + focusActive?: LivekitFocusSelection; + membership?: Partial; }, - membership: Partial = {}, ): CallMembership { + // setup defaults based on overwrites and fallback values. + const { rtcBackendIdentity, callId, fociPreferred, focusActive, membership } = + { + fociPreferred: [exampleTransport], + focusActive: { + type: "livekit" as const, + focus_selection: "oldest_membership" as const, + }, + callId: "", + membership: {}, + ...customOverwrites, + }; + const data: SessionMembershipData = { application: "m.call", call_id: callId, @@ -243,17 +234,29 @@ export function mockRtcMembership( focus_active: focusActive, ...membership, }; + const userId = typeof user === "string" ? user : user.userId; const event = new MatrixEvent({ - sender: typeof user === "string" ? user : user.userId, + sender: userId, event_id: `$-ev-${randomUUID()}:example.org`, content: data, }); - const cms = new CallMembership(event, data); + const membershipData = CallMembership.membershipDataFromMatrixEvent(event); + const cms = new CallMembership( + event, + membershipData, + rtcBackendIdentity ?? `${userId}:${deviceId}`, + ); vi.mocked(cms).getTransport = vi.fn().mockReturnValue(fociPreferred[0]); + return cms; } +export const ownMemberMock: CallMembershipIdentityParts = { + userId: "@alice:example.org", + deviceId: "DEVICE", + memberId: "@alice:example.org:DEVICE", +}; // Maybe it'd be good to move this to matrix-js-sdk? Our testing needs are // rather simple, but if one util to mock a member is good enough for us, maybe // it's useful for matrix-js-sdk consumers in general. @@ -331,6 +334,7 @@ export function createLocalMedia( testScope(), "local", member.userId, + rtcMember.rtcBackendIdentity, constant(localParticipant), { kind: E2eeType.PER_PARTICIPANT, @@ -376,6 +380,7 @@ export function createRemoteMedia( testScope(), "remote", member.userId, + rtcMember.rtcBackendIdentity, constant(participant), { kind: E2eeType.PER_PARTICIPANT, @@ -478,7 +483,7 @@ export class MockRTCSession extends TypedEventEmitter< if (value !== prev) this.emit(MembershipManagerEvent.ProbablyLeft, value); } - public async joinRoomSession(): Promise { + public async joinRTCSession(): Promise { return Promise.resolve(); } } @@ -525,5 +530,8 @@ export function mockMuteStates( joined$: Observable = of(true), ): MuteStates { const observableScope = new ObservableScope(); - return new MuteStates(observableScope, mockMediaDevices({}), joined$); + return new MuteStates(observableScope, mockMediaDevices({}), { + audioEnabled: false, + videoEnabled: false, + }); } diff --git a/src/vitest.setup.ts b/src/vitest.setup.ts index b31d9f0c..f3f5928b 100644 --- a/src/vitest.setup.ts +++ b/src/vitest.setup.ts @@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details. */ import "global-jsdom/register"; -import "@formatjs/intl-durationformat/polyfill"; +import "@formatjs/intl-durationformat/polyfill.js"; import "@formatjs/intl-segmenter/polyfill"; import i18n from "i18next"; import posthog from "posthog-js"; diff --git a/yarn.lock b/yarn.lock index 782c9345..8ef454d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -60,7 +60,7 @@ __metadata: languageName: node linkType: hard -"@ampproject/remapping@npm:^2.2.0, @ampproject/remapping@npm:^2.3.0": +"@ampproject/remapping@npm:^2.3.0": version: 2.3.0 resolution: "@ampproject/remapping@npm:2.3.0" dependencies: @@ -122,33 +122,40 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.27.7, @babel/compat-data@npm:^7.28.0": +"@babel/compat-data@npm:^7.27.7": version: 7.28.0 resolution: "@babel/compat-data@npm:7.28.0" checksum: 10c0/c4e527302bcd61052423f757355a71c3bc62362bac13f7f130de16e439716f66091ff5bdecda418e8fa0271d4c725f860f0ee23ab7bf6e769f7a8bb16dfcb531 languageName: node linkType: hard +"@babel/compat-data@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/compat-data@npm:7.28.5" + checksum: 10c0/702a25de73087b0eba325c1d10979eed7c9b6662677386ba7b5aa6eace0fc0676f78343bae080a0176ae26f58bd5535d73b9d0fbb547fef377692e8b249353a7 + languageName: node + linkType: hard + "@babel/core@npm:^7.16.5, @babel/core@npm:^7.18.5, @babel/core@npm:^7.21.3, @babel/core@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/core@npm:7.28.0" + version: 7.28.5 + resolution: "@babel/core@npm:7.28.5" dependencies: - "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.0" + "@babel/generator": "npm:^7.28.5" "@babel/helper-compilation-targets": "npm:^7.27.2" - "@babel/helper-module-transforms": "npm:^7.27.3" - "@babel/helpers": "npm:^7.27.6" - "@babel/parser": "npm:^7.28.0" + "@babel/helper-module-transforms": "npm:^7.28.3" + "@babel/helpers": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.0" - "@babel/types": "npm:^7.28.0" + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/423302e7c721e73b1c096217880272e02020dfb697a55ccca60ad01bba90037015f84d0c20c6ce297cf33a19bb704bc5c2b3d3095f5284dfa592bd1de0b9e8c3 + checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 languageName: node linkType: hard @@ -191,6 +198,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/generator@npm:7.28.5" + dependencies: + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-annotate-as-pure@npm:7.25.9" @@ -239,6 +259,23 @@ __metadata: languageName: node linkType: hard +"@babel/helper-create-class-features-plugin@npm:^7.28.3, @babel/helper-create-class-features-plugin@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-create-class-features-plugin@npm:7.28.5" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-member-expression-to-functions": "npm:^7.28.5" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.5" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/786a6514efcf4514aaad85beed419b9184d059f4c9a9a95108f320142764999827252a851f7071de19f29424d369616573ecbaa347f1ce23fb12fc6827d9ff56 + languageName: node + linkType: hard + "@babel/helper-create-regexp-features-plugin@npm:^7.18.6": version: 7.26.3 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.26.3" @@ -297,6 +334,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-member-expression-to-functions@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-member-expression-to-functions@npm:7.28.5" + dependencies: + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + checksum: 10c0/4e6e05fbf4dffd0bc3e55e28fcaab008850be6de5a7013994ce874ec2beb90619cda4744b11607a60f8aae0227694502908add6188ceb1b5223596e765b44814 + languageName: node + linkType: hard + "@babel/helper-module-imports@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-module-imports@npm:7.27.1" @@ -307,7 +354,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.27.1, @babel/helper-module-transforms@npm:^7.27.3": +"@babel/helper-module-transforms@npm:^7.27.1": version: 7.27.3 resolution: "@babel/helper-module-transforms@npm:7.27.3" dependencies: @@ -320,6 +367,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/helper-module-transforms@npm:7.28.3" + dependencies: + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.3" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-optimise-call-expression@npm:7.27.1" @@ -407,6 +467,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-validator-option@npm:7.27.1" @@ -425,13 +492,13 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.27.6": - version: 7.27.6 - resolution: "@babel/helpers@npm:7.27.6" +"@babel/helpers@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/helpers@npm:7.28.4" dependencies: "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.27.6" - checksum: 10c0/448bac96ef8b0f21f2294a826df9de6bf4026fd023f8a6bb6c782fe3e61946801ca24381490b8e58d861fee75cd695a1882921afbf1f53b0275ee68c938bd6d3 + "@babel/types": "npm:^7.28.4" + checksum: 10c0/aaa5fb8098926dfed5f223adf2c5e4c7fbba4b911b73dfec2d7d3083f8ba694d201a206db673da2d9b3ae8c01793e795767654558c450c8c14b4c2175b4fcb44 languageName: node linkType: hard @@ -502,15 +569,26 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.27.1" +"@babel/parser@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/parser@npm:7.28.5" + dependencies: + "@babel/types": "npm:^7.28.5" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef + languageName: node + linkType: hard + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.28.5" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/traverse": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.5" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c0/7dfffa978ae1cd179641a7c4b4ad688c6828c2c58ec96b118c2fb10bc3715223de6b88bff1ebff67056bb5fccc568ae773e3b83c592a1b843423319f80c99ebd + checksum: 10c0/844b7c7e9eec6d858262b2f3d5af75d3a6bbd9d3ecc740d95271fbdd84985731674536f5d8ac98f2dc0e8872698b516e406636e4d0cb04b50afe471172095a53 languageName: node linkType: hard @@ -549,15 +627,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.27.1" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.28.3" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/traverse": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.3" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c0/b94e6c3fc019e988b1499490829c327a1067b4ddea8ad402f6d0554793c9124148c2125338c723661b6dff040951abc1f092afbf3f2d234319cd580b68e52445 + checksum: 10c0/3cdc27c4e08a632a58e62c6017369401976edf1cd9ae73fd9f0d6770ddd9accf40b494db15b66bab8db2a8d5dc5bab5ca8c65b19b81fdca955cd8cbbe24daadb languageName: node linkType: hard @@ -674,14 +752,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/plugin-transform-block-scoping@npm:7.28.0" +"@babel/plugin-transform-block-scoping@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-block-scoping@npm:7.28.5" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/787d85e72a92917e735aa54e23062fa777031f8a07046e67f5026eff3d91e64eb535575dd1df917b0011bee014ae51287478af14c1d4ba60bc81e326bc044cfc + checksum: 10c0/6b098887b375c23813ccee7a00179501fc5f709b4ee5a4b2a5c5c9ef3b44cee49e240214b1a9b4ad2bd1911fab3335eac2f0a3c5f014938a1b61bec84cec4845 languageName: node linkType: hard @@ -697,31 +775,31 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-class-static-block@npm:7.27.1" +"@babel/plugin-transform-class-static-block@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/plugin-transform-class-static-block@npm:7.28.3" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-create-class-features-plugin": "npm:^7.28.3" "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.12.0 - checksum: 10c0/396997dd81fc1cf242b921e337d25089d6b9dc3596e81322ff11a6359326dc44f2f8b82dcc279c2e514cafaf8964dc7ed39e9fab4b8af1308b57387d111f6a20 + checksum: 10c0/8c922a64f6f5b359f7515c89ef0037bad583b4484dfebc1f6bc1cf13462547aaceb19788827c57ec9a2d62495f34c4b471ca636bf61af00fdaea5e9642c82b60 languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/plugin-transform-classes@npm:7.28.0" +"@babel/plugin-transform-classes@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/plugin-transform-classes@npm:7.28.4" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.27.3" "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-globals": "npm:^7.28.0" "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-replace-supers": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.0" + "@babel/traverse": "npm:^7.28.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/3b213b43104fe99dd7e79401a86d09e545836e057a70ffe77e8196a87bf67ae167e502ae90afdf0d1a2be683be5652514aaeda743bd984e583523dd8ecfef887 + checksum: 10c0/76687ed37216ff012c599870dc00183fb716f22e1a02fe9481943664c0e4d0d88c3da347dc3fe290d4728f4d47cd594ffa621d23845e2bb8ab446e586308e066 languageName: node linkType: hard @@ -749,6 +827,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-destructuring@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-destructuring@npm:7.28.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/288207f488412b23bb206c7c01ba143714e2506b72a9ec09e993f28366cc8188d121bde714659b3437984a86d2881d9b1b06de3089d5582823ccf2f3b3eaa2c4 + languageName: node + linkType: hard + "@babel/plugin-transform-dotall-regex@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-dotall-regex@npm:7.27.1" @@ -807,14 +897,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.27.1" +"@babel/plugin-transform-exponentiation-operator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.28.5" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/953d21e01fed76da8e08fb5094cade7bf8927c1bb79301916bec2db0593b41dbcfbca1024ad5db886b72208a93ada8f57a219525aad048cf15814eeb65cf760d + checksum: 10c0/006566e003c2a8175346cc4b3260fcd9f719b912ceae8a4e930ce02ee3cf0b2841d5c21795ba71790871783d3c0c1c3d22ce441b8819c37975844bfba027d3f7 languageName: node linkType: hard @@ -876,14 +966,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.27.1" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.28.5" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/5b0abc7c0d09d562bf555c646dce63a30288e5db46fd2ce809a61d064415da6efc3b2b3c59b8e4fe98accd072c89a2f7c3765b400e4bf488651735d314d9feeb + checksum: 10c0/fba4faa96d86fa745b0539bb631deee3f2296f0643c087a50ad0fac2e5f0a787fa885e9bdd90ae3e7832803f3c08e7cd3f1e830e7079dbdc023704923589bb23 languageName: node linkType: hard @@ -922,17 +1012,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.27.1" +"@babel/plugin-transform-modules-systemjs@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.28.5" dependencies: - "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-module-transforms": "npm:^7.28.3" "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - "@babel/traverse": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + "@babel/traverse": "npm:^7.28.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/f16fca62d144d9cbf558e7b5f83e13bb6d0f21fdeff3024b0cecd42ffdec0b4151461da42bd0963512783ece31aafa5ffe03446b4869220ddd095b24d414e2b5 + checksum: 10c0/7e8c0bcff79689702b974f6a0fedb5d0c6eeb5a5e3384deb7028e7cfe92a5242cc80e981e9c1817aad29f2ecc01841753365dd38d877aa0b91737ceec2acfd07 languageName: node linkType: hard @@ -993,18 +1083,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.28.0" +"@babel/plugin-transform-object-rest-spread@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.28.4" dependencies: "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/plugin-transform-destructuring": "npm:^7.28.0" "@babel/plugin-transform-parameters": "npm:^7.27.7" - "@babel/traverse": "npm:^7.28.0" + "@babel/traverse": "npm:^7.28.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/360dc6fd5285ee5e1d3be8a1fb0decd120b2a1726800317b4ab48b7c91616247030239b7fa06ceaa1a8a586fde1e143c24d45f8d41956876099d97d664f8ef1e + checksum: 10c0/81725c8d6349957899975f3f789b1d4fb050ee8b04468ebfaccd5b59e0bda15cbfdef09aee8b4359f322b6715149d680361f11c1a420c4bdbac095537ecf7a90 languageName: node linkType: hard @@ -1043,6 +1133,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-optional-chaining@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.28.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/adf5f70b1f9eb0dd6ff3d159a714683af3c910775653e667bd9f864c3dc2dc9872aba95f6c1e5f2a9675067241942f4fd0d641147ef4bf2bd8bc15f1fa0f2ed5 + languageName: node + linkType: hard + "@babel/plugin-transform-parameters@npm:^7.27.7": version: 7.27.7 resolution: "@babel/plugin-transform-parameters@npm:7.27.7" @@ -1090,14 +1192,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-display-name@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-react-display-name@npm:7.27.1" +"@babel/plugin-transform-react-display-name@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-react-display-name@npm:7.28.0" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/6cd474b5fb30a2255027d8fc19975aee1c1da54dd8bc8b79802676096182ca4136302ce65a24fbb277f8fe30f266006bbf327ef6be2846d3681eb57509744125 + checksum: 10c0/f5f86d2ad92be3e962158f344c2e385e23e2dfae7c8c7dc32138fb2cc46f63f5e50386c9f6c6fc16dbf1792c7bb650ad92c18203d0c2c0bd875bc28b0b80ef30 languageName: node linkType: hard @@ -1161,14 +1263,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.28.0": - version: 7.28.1 - resolution: "@babel/plugin-transform-regenerator@npm:7.28.1" +"@babel/plugin-transform-regenerator@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/plugin-transform-regenerator@npm:7.28.4" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/6c9e6eb80ce9c0bde0876c80979e078fbc85dc802272cba4ee72b5b1c858472e38167c418917e4f0d4384ce888706d95544a8d266880c0e199e167e078168b67 + checksum: 10c0/5ad14647ffaac63c920e28df1b580ee2e932586bbdc71f61ec264398f68a5406c71a7f921de397a41b954a69316c5ab90e5d789ffa2bb34c5e6feb3727cfefb8 languageName: node linkType: hard @@ -1251,18 +1353,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-typescript@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-typescript@npm:7.27.1" +"@babel/plugin-transform-typescript@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-typescript@npm:7.28.5" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.27.1" - "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-create-class-features-plugin": "npm:^7.28.5" "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" "@babel/plugin-syntax-typescript": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/48f1db5de17a0f9fc365ff4fb046010aedc7aad813a7aa42fb73fcdab6442f9e700dde2cc0481086e01b0dae662ae4d3e965a52cde154f0f146d243a8ac68e93 + checksum: 10c0/09e574ba5462e56452b4ceecae65e53c8e697a2d3559ce5d210bed10ac28a18aa69377e7550c30520eb29b40c417ee61997d5d58112657f22983244b78915a7c languageName: node linkType: hard @@ -1314,18 +1416,18 @@ __metadata: linkType: hard "@babel/preset-env@npm:^7.22.20": - version: 7.28.0 - resolution: "@babel/preset-env@npm:7.28.0" + version: 7.28.5 + resolution: "@babel/preset-env@npm:7.28.5" dependencies: - "@babel/compat-data": "npm:^7.28.0" + "@babel/compat-data": "npm:^7.28.5" "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-validator-option": "npm:^7.27.1" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.28.5" "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.27.1" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.27.1" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.27.1" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.27.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.28.3" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-import-assertions": "npm:^7.27.1" "@babel/plugin-syntax-import-attributes": "npm:^7.27.1" @@ -1334,42 +1436,42 @@ __metadata: "@babel/plugin-transform-async-generator-functions": "npm:^7.28.0" "@babel/plugin-transform-async-to-generator": "npm:^7.27.1" "@babel/plugin-transform-block-scoped-functions": "npm:^7.27.1" - "@babel/plugin-transform-block-scoping": "npm:^7.28.0" + "@babel/plugin-transform-block-scoping": "npm:^7.28.5" "@babel/plugin-transform-class-properties": "npm:^7.27.1" - "@babel/plugin-transform-class-static-block": "npm:^7.27.1" - "@babel/plugin-transform-classes": "npm:^7.28.0" + "@babel/plugin-transform-class-static-block": "npm:^7.28.3" + "@babel/plugin-transform-classes": "npm:^7.28.4" "@babel/plugin-transform-computed-properties": "npm:^7.27.1" - "@babel/plugin-transform-destructuring": "npm:^7.28.0" + "@babel/plugin-transform-destructuring": "npm:^7.28.5" "@babel/plugin-transform-dotall-regex": "npm:^7.27.1" "@babel/plugin-transform-duplicate-keys": "npm:^7.27.1" "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.27.1" "@babel/plugin-transform-dynamic-import": "npm:^7.27.1" "@babel/plugin-transform-explicit-resource-management": "npm:^7.28.0" - "@babel/plugin-transform-exponentiation-operator": "npm:^7.27.1" + "@babel/plugin-transform-exponentiation-operator": "npm:^7.28.5" "@babel/plugin-transform-export-namespace-from": "npm:^7.27.1" "@babel/plugin-transform-for-of": "npm:^7.27.1" "@babel/plugin-transform-function-name": "npm:^7.27.1" "@babel/plugin-transform-json-strings": "npm:^7.27.1" "@babel/plugin-transform-literals": "npm:^7.27.1" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.27.1" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.28.5" "@babel/plugin-transform-member-expression-literals": "npm:^7.27.1" "@babel/plugin-transform-modules-amd": "npm:^7.27.1" "@babel/plugin-transform-modules-commonjs": "npm:^7.27.1" - "@babel/plugin-transform-modules-systemjs": "npm:^7.27.1" + "@babel/plugin-transform-modules-systemjs": "npm:^7.28.5" "@babel/plugin-transform-modules-umd": "npm:^7.27.1" "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.27.1" "@babel/plugin-transform-new-target": "npm:^7.27.1" "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.27.1" "@babel/plugin-transform-numeric-separator": "npm:^7.27.1" - "@babel/plugin-transform-object-rest-spread": "npm:^7.28.0" + "@babel/plugin-transform-object-rest-spread": "npm:^7.28.4" "@babel/plugin-transform-object-super": "npm:^7.27.1" "@babel/plugin-transform-optional-catch-binding": "npm:^7.27.1" - "@babel/plugin-transform-optional-chaining": "npm:^7.27.1" + "@babel/plugin-transform-optional-chaining": "npm:^7.28.5" "@babel/plugin-transform-parameters": "npm:^7.27.7" "@babel/plugin-transform-private-methods": "npm:^7.27.1" "@babel/plugin-transform-private-property-in-object": "npm:^7.27.1" "@babel/plugin-transform-property-literals": "npm:^7.27.1" - "@babel/plugin-transform-regenerator": "npm:^7.28.0" + "@babel/plugin-transform-regenerator": "npm:^7.28.4" "@babel/plugin-transform-regexp-modifiers": "npm:^7.27.1" "@babel/plugin-transform-reserved-words": "npm:^7.27.1" "@babel/plugin-transform-shorthand-properties": "npm:^7.27.1" @@ -1389,7 +1491,7 @@ __metadata: semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/f343103b8f0e8da5be4ae031aff8bf35da4764997af4af78ae9506f421b785dd45da1bc09f845b1fc308c8b7d134aead4a1f89e7fb6e213cd2f9fe1d2aa78bc9 + checksum: 10c0/d1b730158de290f1c54ed7db0f4fed3f82db5f868ab0a4cb3fc2ea76ed683b986ae136f6e7eb0b44b91bc9a99039a2559851656b4fd50193af1a815a3e32e524 languageName: node linkType: hard @@ -1407,33 +1509,33 @@ __metadata: linkType: hard "@babel/preset-react@npm:^7.22.15": - version: 7.27.1 - resolution: "@babel/preset-react@npm:7.27.1" + version: 7.28.5 + resolution: "@babel/preset-react@npm:7.28.5" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-validator-option": "npm:^7.27.1" - "@babel/plugin-transform-react-display-name": "npm:^7.27.1" + "@babel/plugin-transform-react-display-name": "npm:^7.28.0" "@babel/plugin-transform-react-jsx": "npm:^7.27.1" "@babel/plugin-transform-react-jsx-development": "npm:^7.27.1" "@babel/plugin-transform-react-pure-annotations": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/a80b02ef08b026cb9830d6512d08c7cd378eef4c0631dacba4aa1106240d9bb76af6373463f0255f4bbdbfcce40375a61e92735375906ba5871629b0c314bc45 + checksum: 10c0/0d785e708ff301f4102bd4738b77e550e32f981e54dfd3de1191b4d68306bbb934d2d465fc78a6bc22fff0a6b3ce3195a53984f52755c4349e7264c7e01e8c7c languageName: node linkType: hard "@babel/preset-typescript@npm:^7.23.0": - version: 7.27.1 - resolution: "@babel/preset-typescript@npm:7.27.1" + version: 7.28.5 + resolution: "@babel/preset-typescript@npm:7.28.5" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-validator-option": "npm:^7.27.1" "@babel/plugin-syntax-jsx": "npm:^7.27.1" "@babel/plugin-transform-modules-commonjs": "npm:^7.27.1" - "@babel/plugin-transform-typescript": "npm:^7.27.1" + "@babel/plugin-transform-typescript": "npm:^7.28.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/cba6ca793d915f8aff9fe2f13b0dfbf5fd3f2e9a17f17478ec9878e9af0d206dcfe93154b9fd353727f16c1dca7c7a3ceb4943f8d28b216235f106bc0fbbcaa3 + checksum: 10c0/b3d55548854c105085dd80f638147aa8295bc186d70492289242d6c857cb03a6c61ec15186440ea10ed4a71cdde7d495f5eb3feda46273f36b0ac926e8409629 languageName: node linkType: hard @@ -1471,10 +1573,10 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.27.6": - version: 7.27.6 - resolution: "@babel/runtime@npm:7.27.6" - checksum: 10c0/89726be83f356f511dcdb74d3ea4d873a5f0cf0017d4530cb53aa27380c01ca102d573eff8b8b77815e624b1f8c24e7f0311834ad4fb632c90a770fda00bd4c8 +"@babel/runtime@npm:^7.27.6, @babel/runtime@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/runtime@npm:7.28.4" + checksum: 10c0/792ce7af9750fb9b93879cc9d1db175701c4689da890e6ced242ea0207c9da411ccf16dc04e689cc01158b28d7898c40d75598f4559109f761c12ce01e959bf7 languageName: node linkType: hard @@ -1545,6 +1647,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4, @babel/traverse@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/traverse@npm:7.28.5" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.5" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.5" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.5" + debug: "npm:^4.3.1" + checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.10.3, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3": version: 7.26.0 resolution: "@babel/types@npm:7.26.0" @@ -1585,16 +1702,6 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.27.6": - version: 7.27.7 - resolution: "@babel/types@npm:7.27.7" - dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - checksum: 10c0/1d1dcb5fa7cfba2b4034a3ab99ba17049bfc4af9e170935575246cdb1cee68b04329a0111506d9ae83fb917c47dbd4394a6db5e32fbd041b7834ffbb17ca086b - languageName: node - linkType: hard - "@babel/types@npm:^7.28.0": version: 7.28.2 resolution: "@babel/types@npm:7.28.2" @@ -1605,6 +1712,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/types@npm:7.28.5" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^1.0.2": version: 1.0.2 resolution: "@bcoe/v8-coverage@npm:1.0.2" @@ -1662,10 +1779,10 @@ __metadata: languageName: node linkType: hard -"@csstools/color-helpers@npm:^5.0.2": - version: 5.0.2 - resolution: "@csstools/color-helpers@npm:5.0.2" - checksum: 10c0/bebaddb28b9eb58b0449edd5d0c0318fa88f3cb079602ee27e88c9118070d666dcc4e09a5aa936aba2fde6ba419922ade07b7b506af97dd7051abd08dfb2959b +"@csstools/color-helpers@npm:^5.1.0": + version: 5.1.0 + resolution: "@csstools/color-helpers@npm:5.1.0" + checksum: 10c0/b7f99d2e455cf1c9b41a67a5327d5d02888cd5c8802a68b1887dffef537d9d4bc66b3c10c1e62b40bbed638b6c1d60b85a232f904ed7b39809c4029cb36567db languageName: node linkType: hard @@ -1689,19 +1806,6 @@ __metadata: languageName: node linkType: hard -"@csstools/css-color-parser@npm:^3.0.10": - version: 3.0.10 - resolution: "@csstools/css-color-parser@npm:3.0.10" - dependencies: - "@csstools/color-helpers": "npm:^5.0.2" - "@csstools/css-calc": "npm:^2.1.4" - peerDependencies: - "@csstools/css-parser-algorithms": ^3.0.5 - "@csstools/css-tokenizer": ^3.0.4 - checksum: 10c0/8f8a2395b117c2f09366b5c9bf49bc740c92a65b6330fe3cc1e76abafd0d1000e42a657d7b0a3814846a66f1d69896142f7e36d7a4aca77de977e5cc5f944747 - languageName: node - linkType: hard - "@csstools/css-color-parser@npm:^3.0.7": version: 3.0.7 resolution: "@csstools/css-color-parser@npm:3.0.7" @@ -1715,6 +1819,19 @@ __metadata: languageName: node linkType: hard +"@csstools/css-color-parser@npm:^3.1.0": + version: 3.1.0 + resolution: "@csstools/css-color-parser@npm:3.1.0" + dependencies: + "@csstools/color-helpers": "npm:^5.1.0" + "@csstools/css-calc": "npm:^2.1.4" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10c0/0e0c670ad54ec8ec4d9b07568b80defd83b9482191f5e8ca84ab546b7be6db5d7cc2ba7ac9fae54488b129a4be235d6183d3aab4416fec5e89351f73af4222c5 + languageName: node + linkType: hard + "@csstools/css-parser-algorithms@npm:^3.0.4": version: 3.0.4 resolution: "@csstools/css-parser-algorithms@npm:3.0.4" @@ -1757,6 +1874,21 @@ __metadata: languageName: node linkType: hard +"@csstools/postcss-alpha-function@npm:^1.0.1": + version: 1.0.1 + resolution: "@csstools/postcss-alpha-function@npm:1.0.1" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/35ca209e572534ade21ac5c18aad702aa492eb39e2d0e475f441371063418fe9650554e6a59b1318d3a615da83ef54d9a588faa27063ecc0a568ef7290a6b488 + languageName: node + linkType: hard + "@csstools/postcss-cascade-layers@npm:^5.0.2": version: 5.0.2 resolution: "@csstools/postcss-cascade-layers@npm:5.0.2" @@ -1769,62 +1901,92 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-color-function@npm:^4.0.10": - version: 4.0.10 - resolution: "@csstools/postcss-color-function@npm:4.0.10" +"@csstools/postcss-color-function-display-p3-linear@npm:^1.0.1": + version: 1.0.1 + resolution: "@csstools/postcss-color-function-display-p3-linear@npm:1.0.1" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/a6e65d37a114f95634a07660daa1aa52f4abfb6ddd740cc9267967a5948f5c72469a6ba2432ab1f31616d6f1a4ab963b69f778497496986535831b0b2b399f75 + checksum: 10c0/d02d45410c9257f5620c766f861f8fa3762b74ef01fdba8060b33a4c98f929e2219cd476b25bd4181ac186158a4d99a0da555c0b6ba45a7ac4a3a5885baad1f5 languageName: node linkType: hard -"@csstools/postcss-color-mix-function@npm:^3.0.10": - version: 3.0.10 - resolution: "@csstools/postcss-color-mix-function@npm:3.0.10" +"@csstools/postcss-color-function@npm:^4.0.12": + version: 4.0.12 + resolution: "@csstools/postcss-color-function@npm:4.0.12" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/9505a09a805f52555bd06c8f54d537a99578efe5c7e643c9fdaca8cbb7d74d4d3e07b829c6aed315c75ec5ce113261fb402e01b67e4a423ed39ea8991a6dded0 + checksum: 10c0/a355b04d90f89c8e37a4a23543151558060acc68fb2e7d1c3549bebeeae2b147eec26af1fbc6ee690f0ba4830263f2d181f5331d16d3483b5542be46996fa755 languageName: node linkType: hard -"@csstools/postcss-color-mix-variadic-function-arguments@npm:^1.0.0": - version: 1.0.0 - resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:1.0.0" +"@csstools/postcss-color-mix-function@npm:^3.0.12": + version: 3.0.12 + resolution: "@csstools/postcss-color-mix-function@npm:3.0.12" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/dd45bd19931cc4780247173b793e5f1e6409b76f92b04fe26e07b0fa048aedc7bcbd92356a558581f695654c2f2d189e1b40b14a9c3f246e86e83b0edf646066 + checksum: 10c0/3e98a5118852083d1f87a3f842f78088192b1f9f08fdf1f3b3ef1e8969e18fdadc1e3bcac3d113a07c8917a7e8fa65fdec55a31df9a1b726c8d7ae89db86e8e5 languageName: node linkType: hard -"@csstools/postcss-content-alt-text@npm:^2.0.6": - version: 2.0.6 - resolution: "@csstools/postcss-content-alt-text@npm:2.0.6" +"@csstools/postcss-color-mix-variadic-function-arguments@npm:^1.0.2": + version: 1.0.2 + resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:1.0.2" dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/e7d21002a84d0fba4fe815fb7d3d19b81fb1719a7b6fdd240eb6639d58937b64d6f5c9aa11ffe8a64891a2ed181818cd56d346f58949c2eaa9df7c82ee95ef8e + checksum: 10c0/34073f0f0d33e4958f90763e692955a8e8c678b74284234497c4aa0d2143756e1b3616e0c09832caad498870e227ca0a681316afe3a71224fc40ade0ead1bdd9 + languageName: node + linkType: hard + +"@csstools/postcss-content-alt-text@npm:^2.0.8": + version: 2.0.8 + resolution: "@csstools/postcss-content-alt-text@npm:2.0.8" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/4c330cc2a1e434688a62613ecceb1434cd725ce024c1ad8d4a4c76b9839d1f3ea8566a8c6494921e2b46ec7feef6af8ed6548c216dcb8f0feab4b1d52c96228e + languageName: node + linkType: hard + +"@csstools/postcss-contrast-color-function@npm:^2.0.12": + version: 2.0.12 + resolution: "@csstools/postcss-contrast-color-function@npm:2.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/b783ce948cdf1513ee238e9115b42881a8d3e5d13c16038601b1c470d661cfaeeece4eea29904fb9fcae878bad86f766810fa798a703ab9ad4b0cf276b173f8f languageName: node linkType: hard @@ -1853,59 +2015,59 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-gamut-mapping@npm:^2.0.10": - version: 2.0.10 - resolution: "@csstools/postcss-gamut-mapping@npm:2.0.10" +"@csstools/postcss-gamut-mapping@npm:^2.0.11": + version: 2.0.11 + resolution: "@csstools/postcss-gamut-mapping@npm:2.0.11" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" peerDependencies: postcss: ^8.4 - checksum: 10c0/87cd8289478bf88195469fcf4f80c8fed9e0e5ef76a335a10c4c21582542acb16cced1e00e7da90deaf2e62e383a5c6fe402f429f227c87a2c20e2545a69c537 + checksum: 10c0/490b8ccf10e30879a4415afbdd3646e1cdac3671586b7916855cf47a536f3be75eed014396056bde6528e0cb76d904e79bad78afc0b499e837264cf22519d145 languageName: node linkType: hard -"@csstools/postcss-gradients-interpolation-method@npm:^5.0.10": - version: 5.0.10 - resolution: "@csstools/postcss-gradients-interpolation-method@npm:5.0.10" +"@csstools/postcss-gradients-interpolation-method@npm:^5.0.12": + version: 5.0.12 + resolution: "@csstools/postcss-gradients-interpolation-method@npm:5.0.12" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/206d079d7679a9609a4fb227ddaf3443d04cff88b55bcfec1cf63c9de372b8720edde8614fc51d2237e4edbff8ce34697f912bc25c2ae41390353fce88455515 + checksum: 10c0/70b3d6c7050ce882ed2281e71eb4493531ae8d55d21899920eeeb6c205d90aaf430419a66235484ccce3a1a1891367dfc0ef772f3866ae3a9d8ec5ddd0cfe894 languageName: node linkType: hard -"@csstools/postcss-hwb-function@npm:^4.0.10": - version: 4.0.10 - resolution: "@csstools/postcss-hwb-function@npm:4.0.10" +"@csstools/postcss-hwb-function@npm:^4.0.12": + version: 4.0.12 + resolution: "@csstools/postcss-hwb-function@npm:4.0.12" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/defb9b319b14228307196b9a88e3cbf0acd1d3768b936716dca846875068ad4453e7a2a3d75d1fab5534c8655e9c555e1fa70d30e2c85d68ed2117a7cfe7837c + checksum: 10c0/d0dac34da9d7ac654060b6b27690a419718e990b21ff3e63266ea59934a865bc6aeae8eb8e1ca3e227a8b2a208657e3ab70ccdf0437f1f09d21ab848bbffcaa2 languageName: node linkType: hard -"@csstools/postcss-ic-unit@npm:^4.0.2": - version: 4.0.2 - resolution: "@csstools/postcss-ic-unit@npm:4.0.2" +"@csstools/postcss-ic-unit@npm:^4.0.4": + version: 4.0.4 + resolution: "@csstools/postcss-ic-unit@npm:4.0.4" dependencies: - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/26adb8351143e591080f542d87b223ee5ebc5f33f6d03b217505b249ceb19c46a06732a88000e3a1857ae712a6ea0ffa089a24ad8b8042421490539de5c3d0e8 + checksum: 10c0/20168e70ecb4abf7a69e407d653b6c7c9c82f2c7b1da0920e1d035f62b5ef8552cc7f1b62e0dca318df13c348e79fba862e1a4bb0e9432119a82b10aeb511752 languageName: node linkType: hard @@ -1930,17 +2092,17 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-light-dark-function@npm:^2.0.9": - version: 2.0.9 - resolution: "@csstools/postcss-light-dark-function@npm:2.0.9" +"@csstools/postcss-light-dark-function@npm:^2.0.11": + version: 2.0.11 + resolution: "@csstools/postcss-light-dark-function@npm:2.0.11" dependencies: "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/ee2937f0e5dcaafd10349f0914596e8e1ef6f9d46939c6a6b0e2e63cab0552594e5140bf56e485048c3bca6634dd9673a176c57b9e77001332787f4263835c0f + checksum: 10c0/0175be41bb0044a48bc98d5c55cce41ed6b9ada88253c5f20d0ca17287cba4b429742b458ac5744675b9a286109e13ac51d64e226ab16040d7b051ba64c0c77b languageName: node linkType: hard @@ -2044,29 +2206,50 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-oklab-function@npm:^4.0.10": - version: 4.0.10 - resolution: "@csstools/postcss-oklab-function@npm:4.0.10" +"@csstools/postcss-oklab-function@npm:^4.0.12": + version: 4.0.12 + resolution: "@csstools/postcss-oklab-function@npm:4.0.12" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/421d1f2574941c3caecd608588533581fc0766998cc85474008a49b5f1011249cb2be7ef9f21a346fd3895598da18e58860fde06d34b1b833918fa880c41c18f + checksum: 10c0/40d4f51b568c8299c054f8971d0e85fa7da609ba23ce6c84dc17e16bc3838640ed6da75c3886dc9a96a11005773c6e23cba13a5510c781b2d633d07ad7bda6b7 languageName: node linkType: hard -"@csstools/postcss-progressive-custom-properties@npm:^4.1.0": - version: 4.1.0 - resolution: "@csstools/postcss-progressive-custom-properties@npm:4.1.0" +"@csstools/postcss-position-area-property@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-position-area-property@npm:1.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/38f770454d46bfed01d43a3f5e7ac07d3111399b374a7198ae6503cdb6288e410c7b4199f5a7af8f16aeb688216445ade97be417c084313d6c56f55e50d34559 + languageName: node + linkType: hard + +"@csstools/postcss-progressive-custom-properties@npm:^4.2.1": + version: 4.2.1 + resolution: "@csstools/postcss-progressive-custom-properties@npm:4.2.1" dependencies: postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/175081a5c53e37a282f596e01359d4411800e4017c2d389caaa2b7c9b7507a50c5f1ac3d937f27f000be3ac2ac788cad9c1490ec6bc1d4de51331f3cc8ccda8e + checksum: 10c0/56e9a147799719fd5c550c035437693dd50cdfef46d66a4f2ce8f196e1006a096aa47d412710a89c3dc9808068a0a101c7f607a507ed68e925580c6f921e84d5 + languageName: node + linkType: hard + +"@csstools/postcss-property-rule-prelude-list@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-property-rule-prelude-list@npm:1.0.0" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/ae8bbca3a77ca59c21c11899a904f9d9417a19a3359d01dee042e0489b7ddfe7cea13ae275b7e7936d9b0b99c0a13f7f685f962cd63ca3d3d2b6e5eacc293a0d languageName: node linkType: hard @@ -2083,18 +2266,18 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-relative-color-syntax@npm:^3.0.10": - version: 3.0.10 - resolution: "@csstools/postcss-relative-color-syntax@npm:3.0.10" +"@csstools/postcss-relative-color-syntax@npm:^3.0.12": + version: 3.0.12 + resolution: "@csstools/postcss-relative-color-syntax@npm:3.0.12" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/de9c41a936a77dab68cdb2dd23a26ba1b92d90bf2a7cf463fada2f2daf6ad0d7394fa2b1ed444f509006992961d993383a34a9afd3a48a9dc67a3793afcd9bb8 + checksum: 10c0/11af386c8193e22c148ac034eee94c56da3060bdbde3196d2d641b088e12de35bef187bcd7d421f9e4d49c4f1cfc28b24e136e62107e02ed7007a3a28f635d06 languageName: node linkType: hard @@ -2135,15 +2318,38 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-text-decoration-shorthand@npm:^4.0.2": - version: 4.0.2 - resolution: "@csstools/postcss-text-decoration-shorthand@npm:4.0.2" +"@csstools/postcss-syntax-descriptor-syntax-production@npm:^1.0.1": + version: 1.0.1 + resolution: "@csstools/postcss-syntax-descriptor-syntax-production@npm:1.0.1" dependencies: - "@csstools/color-helpers": "npm:^5.0.2" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/b9b3d84a50b86b1af1b8b7e56a64d5eebc1c89c323a5263306c5c69ddb05a4d468d7072a7786b0ea6601629035df0089565e9d98d55d0f4eb7201cf7ed1bb3e9 + languageName: node + linkType: hard + +"@csstools/postcss-system-ui-font-family@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-system-ui-font-family@npm:1.0.0" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/6a81761ae3cae643659b1416a7a892cf1505474896193b8abc26cff319cb6b1a20b64c5330d64019fba458e058da3abc9407d0ebf0c102289c0b79ef99b4c6d6 + languageName: node + linkType: hard + +"@csstools/postcss-text-decoration-shorthand@npm:^4.0.3": + version: 4.0.3 + resolution: "@csstools/postcss-text-decoration-shorthand@npm:4.0.3" + dependencies: + "@csstools/color-helpers": "npm:^5.1.0" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/01e2f3717e7a42224dc1a746491c55a381cf208cb7588f0308eeefe730675be4c7bb56c0cc557e75999c981e67da7d0b0bb68610635752c89ef251ee435b9cac + checksum: 10c0/f6af7d5dcf599edcf76c5e396ef2d372bbe1c1f3fbaaccd91e91049e64b6ff68b44f459277aef0a8110baca3eaa21275012adc52ccb8c0fc526a4c35577f8fce languageName: node linkType: hard @@ -2196,31 +2402,31 @@ __metadata: languageName: node linkType: hard -"@emnapi/core@npm:^1.4.3": - version: 1.4.3 - resolution: "@emnapi/core@npm:1.4.3" +"@emnapi/core@npm:^1.7.1": + version: 1.8.0 + resolution: "@emnapi/core@npm:1.8.0" dependencies: - "@emnapi/wasi-threads": "npm:1.0.2" + "@emnapi/wasi-threads": "npm:1.1.0" tslib: "npm:^2.4.0" - checksum: 10c0/e30101d16d37ef3283538a35cad60e22095aff2403fb9226a35330b932eb6740b81364d525537a94eb4fb51355e48ae9b10d779c0dd1cdcd55d71461fe4b45c7 + checksum: 10c0/4bb07df0a6ba6478bd4bee2a3609df1f9ef835588227261e9d8ef40f25a9e7596d837f663a4b2c2193d7e8a130370cab5a8063255d81c30903a8c167435d2687 languageName: node linkType: hard -"@emnapi/runtime@npm:^1.4.3": - version: 1.4.3 - resolution: "@emnapi/runtime@npm:1.4.3" +"@emnapi/runtime@npm:^1.7.1": + version: 1.8.0 + resolution: "@emnapi/runtime@npm:1.8.0" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/3b7ab72d21cb4e034f07df80165265f85f445ef3f581d1bc87b67e5239428baa00200b68a7d5e37a0425c3a78320b541b07f76c5530f6f6f95336a6294ebf30b + checksum: 10c0/25330ade92c48d19f4dc896ecfe906a45c95bd2d3d4155fcf4934e796703f90f23c8f2e7466be44ec734b68ea3e50f64831647d75e1bd44aebbbe1ca870f932a languageName: node linkType: hard -"@emnapi/wasi-threads@npm:1.0.2": - version: 1.0.2 - resolution: "@emnapi/wasi-threads@npm:1.0.2" +"@emnapi/wasi-threads@npm:1.1.0": + version: 1.1.0 + resolution: "@emnapi/wasi-threads@npm:1.1.0" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/f0621b1fc715221bd2d8332c0ca922617bcd77cdb3050eae50a124eb8923c54fa425d23982dc8f29d505c8798a62d1049bace8b0686098ff9dd82270e06d772e + checksum: 10c0/e6d54bf2b1e64cdd83d2916411e44e579b6ae35d5def0dea61a3c452d9921373044dff32a8b8473ae60c80692bdc39323e98b96a3f3d87ba6886b24dd0ef7ca1 languageName: node linkType: hard @@ -2251,6 +2457,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/aix-ppc64@npm:0.27.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/android-arm64@npm:0.25.1" @@ -2258,6 +2471,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm64@npm:0.27.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/android-arm@npm:0.25.1" @@ -2265,6 +2485,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm@npm:0.27.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/android-x64@npm:0.25.1" @@ -2272,6 +2499,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-x64@npm:0.27.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/darwin-arm64@npm:0.25.1" @@ -2279,6 +2513,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-arm64@npm:0.27.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/darwin-x64@npm:0.25.1" @@ -2286,6 +2527,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-x64@npm:0.27.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/freebsd-arm64@npm:0.25.1" @@ -2293,6 +2541,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-arm64@npm:0.27.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/freebsd-x64@npm:0.25.1" @@ -2300,6 +2555,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-x64@npm:0.27.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-arm64@npm:0.25.1" @@ -2307,6 +2569,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm64@npm:0.27.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-arm@npm:0.25.1" @@ -2314,6 +2583,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm@npm:0.27.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-ia32@npm:0.25.1" @@ -2321,6 +2597,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ia32@npm:0.27.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-loong64@npm:0.25.1" @@ -2328,6 +2611,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-loong64@npm:0.27.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-mips64el@npm:0.25.1" @@ -2335,6 +2625,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-mips64el@npm:0.27.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-ppc64@npm:0.25.1" @@ -2342,6 +2639,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ppc64@npm:0.27.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-riscv64@npm:0.25.1" @@ -2349,6 +2653,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-riscv64@npm:0.27.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-s390x@npm:0.25.1" @@ -2356,6 +2667,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-s390x@npm:0.27.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-x64@npm:0.25.1" @@ -2363,6 +2681,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-x64@npm:0.27.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/netbsd-arm64@npm:0.25.1" @@ -2370,6 +2695,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-arm64@npm:0.27.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/netbsd-x64@npm:0.25.1" @@ -2377,6 +2709,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-x64@npm:0.27.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/openbsd-arm64@npm:0.25.1" @@ -2384,6 +2723,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-arm64@npm:0.27.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/openbsd-x64@npm:0.25.1" @@ -2391,6 +2737,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-x64@npm:0.27.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openharmony-arm64@npm:0.27.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/sunos-x64@npm:0.25.1" @@ -2398,6 +2758,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/sunos-x64@npm:0.27.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/win32-arm64@npm:0.25.1" @@ -2405,6 +2772,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-arm64@npm:0.27.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/win32-ia32@npm:0.25.1" @@ -2412,6 +2786,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-ia32@npm:0.27.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/win32-x64@npm:0.25.1" @@ -2419,6 +2800,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-x64@npm:0.27.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -2515,6 +2903,16 @@ __metadata: languageName: node linkType: hard +"@floating-ui/dom@npm:1.6.13, @floating-ui/dom@npm:^1.0.0": + version: 1.6.13 + resolution: "@floating-ui/dom@npm:1.6.13" + dependencies: + "@floating-ui/core": "npm:^1.6.0" + "@floating-ui/utils": "npm:^0.2.9" + checksum: 10c0/272242d2eb6238ffcee0cb1f3c66e0eafae804d5d7b449db5ecf904bc37d31ad96cf575a9e650b93c1190f64f49a684b1559d10e05ed3ec210628b19116991a9 + languageName: node + linkType: hard + "@floating-ui/dom@npm:1.7.4": version: 1.7.4 resolution: "@floating-ui/dom@npm:1.7.4" @@ -2525,16 +2923,6 @@ __metadata: languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.0": - version: 1.6.13 - resolution: "@floating-ui/dom@npm:1.6.13" - dependencies: - "@floating-ui/core": "npm:^1.6.0" - "@floating-ui/utils": "npm:^0.2.9" - checksum: 10c0/272242d2eb6238ffcee0cb1f3c66e0eafae804d5d7b449db5ecf904bc37d31ad96cf575a9e650b93c1190f64f49a684b1559d10e05ed3ec210628b19116991a9 - languageName: node - linkType: hard - "@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.1.2": version: 2.1.2 resolution: "@floating-ui/react-dom@npm:2.1.2" @@ -2576,28 +2964,40 @@ __metadata: linkType: hard "@fontsource/inconsolata@npm:^5.1.0": - version: 5.2.6 - resolution: "@fontsource/inconsolata@npm:5.2.6" - checksum: 10c0/3e76e02ca075ed81d4e1f7b3de39051cf98a1c5e0acc5ef348f0e2fd5ba9047c79fefc245cc78dbfa98b0fb4dafe8882cd3df57a451c02b104ccc877cccf8433 + version: 5.2.8 + resolution: "@fontsource/inconsolata@npm:5.2.8" + checksum: 10c0/2d5788a41bc60d7d00e5ba75689241a5146a4f60a3ec79d14dd2a0a5aa1ec2e697aa6aed3d1c0564208d32916aea588535f16a57f7a775b554d7f9adb03ad64a languageName: node linkType: hard "@fontsource/inter@npm:^5.1.0": - version: 5.2.6 - resolution: "@fontsource/inter@npm:5.2.6" - checksum: 10c0/7a1347608aab06e53665272ed41fd906ffa96c57b004e8aae91b98177bba97c54b60305d956254c4771387c383f9bdbd917006fb34c9721c020e6017d33e9a5c + version: 5.2.8 + resolution: "@fontsource/inter@npm:5.2.8" + checksum: 10c0/f737dd50005e4809887ba55ae0c9b7174216d6d14875d17a4fbb9a0ad75dec4265928b805a43fe16a23f14a878f1974a398bbfc84ad65c79fc4d4b9c3ea154e1 languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.3.4": - version: 2.3.4 - resolution: "@formatjs/ecma402-abstract@npm:2.3.4" +"@formatjs/ecma402-abstract@npm:2.3.6": + version: 2.3.6 + resolution: "@formatjs/ecma402-abstract@npm:2.3.6" dependencies: "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/intl-localematcher": "npm:0.6.1" + "@formatjs/intl-localematcher": "npm:0.6.2" decimal.js: "npm:^10.4.3" tslib: "npm:^2.8.0" - checksum: 10c0/2644bc618a34dc610ef9691281eeb45ae6175e6982cf19f1bd140672fc95c748747ce3c85b934649ea7e4a304f7ae0060625fd53d5df76f92ca3acf743e1eb0a + checksum: 10c0/63be2a73d3168bf45ab5d50db58376e852db5652d89511ae6e44f1fa03ad96ebbfe9b06a1dfaa743db06e40eb7f33bd77530b9388289855cca79a0e3fc29eacf + languageName: node + linkType: hard + +"@formatjs/ecma402-abstract@npm:3.0.7": + version: 3.0.7 + resolution: "@formatjs/ecma402-abstract@npm:3.0.7" + dependencies: + "@formatjs/fast-memoize": "npm:3.0.2" + "@formatjs/intl-localematcher": "npm:0.7.4" + decimal.js: "npm:^10.4.3" + tslib: "npm:^2.8.0" + checksum: 10c0/0fdc25ef72dcd5bbe1deeb190be2f0a2e2770a2135904d16ddfb424305a1efed14b026fba6c48121bc32f693abf1fe08c0ee12cb7d888cb2ba92963236d82c77 languageName: node linkType: hard @@ -2610,34 +3010,53 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-durationformat@npm:^0.7.0": - version: 0.7.4 - resolution: "@formatjs/intl-durationformat@npm:0.7.4" +"@formatjs/fast-memoize@npm:3.0.2": + version: 3.0.2 + resolution: "@formatjs/fast-memoize@npm:3.0.2" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" - "@formatjs/intl-localematcher": "npm:0.6.1" tslib: "npm:^2.8.0" - checksum: 10c0/e340cab41fcb52639c73f25a422a2cf252c0fb8d328b88bd72b8f995e2d7ffa5d6aa7bb4f4e11862d45bd7eef398d9c8a1cf9d400bcf89c42ff3c38e63196dbe + checksum: 10c0/f7d1074090df309d37322979fe5fc96451531317b42bd927102a3a86dee537b1cb0e378158c74e00efd9714a0aa0f1e5a673c749535df200e13167112676ce88 languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.6.1": - version: 0.6.1 - resolution: "@formatjs/intl-localematcher@npm:0.6.1" +"@formatjs/intl-durationformat@npm:^0.9.0": + version: 0.9.1 + resolution: "@formatjs/intl-durationformat@npm:0.9.1" + dependencies: + "@formatjs/ecma402-abstract": "npm:3.0.7" + "@formatjs/intl-localematcher": "npm:0.7.4" + tslib: "npm:^2.8.0" + checksum: 10c0/6f7b01027c07162b26be3014bba17a7633d1f9cfe6c26c5f403e72b92ac26c67cda2d88aeedab891b080664cfb4aace0eacec70b6f006616fe0d322dc2e8145d + languageName: node + linkType: hard + +"@formatjs/intl-localematcher@npm:0.6.2": + version: 0.6.2 + resolution: "@formatjs/intl-localematcher@npm:0.6.2" dependencies: tslib: "npm:^2.8.0" - checksum: 10c0/bacbedd508519c1bb5ca2620e89dc38f12101be59439aa14aa472b222915b462cb7d679726640f6dcf52a05dd218b5aa27ccd60f2e5010bb96f1d4929848cde0 + checksum: 10c0/22a17a4c67160b6c9f52667914acfb7b79cd6d80630d4ac6d4599ce447cb89d2a64f7d58fa35c3145ddb37fef893f0a45b9a55e663a4eb1f2ae8b10a89fac235 + languageName: node + linkType: hard + +"@formatjs/intl-localematcher@npm:0.7.4": + version: 0.7.4 + resolution: "@formatjs/intl-localematcher@npm:0.7.4" + dependencies: + "@formatjs/fast-memoize": "npm:3.0.2" + tslib: "npm:^2.8.0" + checksum: 10c0/7fc31e13397317faadee033dcf668cda49f031b28542c634c920339f374f483235543e08be2077152cfe5dd41e651d8d2d37b6ece8aa044c0998c48f5472fb1a languageName: node linkType: hard "@formatjs/intl-segmenter@npm:^11.7.3": - version: 11.7.10 - resolution: "@formatjs/intl-segmenter@npm:11.7.10" + version: 11.7.12 + resolution: "@formatjs/intl-segmenter@npm:11.7.12" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" - "@formatjs/intl-localematcher": "npm:0.6.1" + "@formatjs/ecma402-abstract": "npm:2.3.6" + "@formatjs/intl-localematcher": "npm:0.6.2" tslib: "npm:^2.8.0" - checksum: 10c0/f84974d00a020cf9f7c153c56f2bb20a104b1cce33c6672c491c842f2b2367d6746bc903a7f18e922da1a89c7ee6edd9e79bf6971dd26c41b6c9faaa76e6c57e + checksum: 10c0/5ab30a9b9e9a63b9c29c3b90a94e7e5eef958829ed0b03a0117998fe0c1e213546eeebe0636b4559d27db86868bffc5311e3619248a2416045c99de6982fa46a languageName: node linkType: hard @@ -2726,6 +3145,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + "@jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.2 resolution: "@jridgewell/resolve-uri@npm:3.1.2" @@ -2784,7 +3213,21 @@ __metadata: languageName: node linkType: hard -"@livekit/components-core@npm:0.12.12, @livekit/components-core@npm:^0.12.0": +"@livekit/components-core@npm:0.12.11": + version: 0.12.11 + resolution: "@livekit/components-core@npm:0.12.11" + dependencies: + "@floating-ui/dom": "npm:1.6.13" + loglevel: "npm:1.9.1" + rxjs: "npm:7.8.2" + peerDependencies: + livekit-client: ^2.15.14 + tslib: ^2.6.2 + checksum: 10c0/9c2ac3d30bb8cc9067ae0b2049784f81e90e57df9eabf7edbaf3c8ceb65a63f644a4e6abeb6cc38d3ebe52663d8dbb88535e01a965011f365d5ae1f3daf86052 + languageName: node + linkType: hard + +"@livekit/components-core@npm:^0.12.0": version: 0.12.12 resolution: "@livekit/components-core@npm:0.12.12" dependencies: @@ -2799,10 +3242,10 @@ __metadata: linkType: hard "@livekit/components-react@npm:^2.0.0": - version: 2.9.17 - resolution: "@livekit/components-react@npm:2.9.17" + version: 2.9.16 + resolution: "@livekit/components-react@npm:2.9.16" dependencies: - "@livekit/components-core": "npm:0.12.12" + "@livekit/components-core": "npm:0.12.11" clsx: "npm:2.1.1" events: "npm:^3.3.0" jose: "npm:^6.0.12" @@ -2816,7 +3259,7 @@ __metadata: peerDependenciesMeta: "@livekit/krisp-noise-filter": optional: true - checksum: 10c0/ba64ada37d4b3ce4d5ee7c5b2a6bddbffc17c2e641e95881aac9f02b4ff7428105e0a372d364e50ff124e988b7426d322d94caabdb55b634aebf0144d7e37f99 + checksum: 10c0/4ba4ff473c5a29d3107412733a6676a3b708d70684ed463e9b34cda26abb3d2f317c2828a52e730837b756de9df3fc248260d6f390aedebfb6ec96ef63c7b151 languageName: node linkType: hard @@ -2827,7 +3270,7 @@ __metadata: languageName: node linkType: hard -"@livekit/protocol@npm:1.42.2, @livekit/protocol@npm:^1.42.2": +"@livekit/protocol@npm:1.42.2": version: 1.42.2 resolution: "@livekit/protocol@npm:1.42.2" dependencies: @@ -2836,22 +3279,31 @@ __metadata: languageName: node linkType: hard -"@livekit/track-processors@npm:^0.5.5": - version: 0.5.8 - resolution: "@livekit/track-processors@npm:0.5.8" +"@livekit/protocol@npm:^1.42.2": + version: 1.43.4 + resolution: "@livekit/protocol@npm:1.43.4" + dependencies: + "@bufbuild/protobuf": "npm:^1.10.0" + checksum: 10c0/38077ceec44151b7481a95ce25869570b1466359de4992d9367002fc5b0925fc8ca120ed448099ae552064f23664ebe0920669f4fba97164eacbf181664683f2 + languageName: node + linkType: hard + +"@livekit/track-processors@npm:^0.6.0 || ^0.7.1": + version: 0.6.1 + resolution: "@livekit/track-processors@npm:0.6.1" dependencies: "@mediapipe/tasks-vision": "npm:0.10.14" peerDependencies: "@types/dom-mediacapture-transform": ^0.1.9 livekit-client: ^1.12.0 || ^2.1.0 - checksum: 10c0/1ea87fd79b518a7736b5c0052522ba31cc161fe1181ac0d438fa8a01a25347729b375a4865ea8da3422a922a4cffbc6cf58af13141d79b75f5ea19196e2b97b4 + checksum: 10c0/80f54663c7e13de299de9e2565b6cbd2ba74ea0a4a8adf8a366e8cfd0e19dedfb9d699899137f1a6133414f28779877eeb3200074c03893bc63aeb0d8c912a91 languageName: node linkType: hard -"@matrix-org/matrix-sdk-crypto-wasm@npm:^16.0.0": - version: 16.0.0 - resolution: "@matrix-org/matrix-sdk-crypto-wasm@npm:16.0.0" - checksum: 10c0/13b4ede3e618da819957abff778afefcf3baf9a2faac04a36bb5a07a44fae2ea05fbfa072eb3408d48b2b7b9aaf27242ce52c594c8ce9bf1fb8b3aade2832be1 +"@matrix-org/matrix-sdk-crypto-wasm@npm:^17.0.0": + version: 17.0.0 + resolution: "@matrix-org/matrix-sdk-crypto-wasm@npm:17.0.0" + checksum: 10c0/fa97e3111099057e0953e7550d6556b6e7553f3badd5b25a6988d2fcc94d22288a27e63cb204771b74ff24388d770c83f2cf5aec583f05c6ecf46509b8020570 languageName: node linkType: hard @@ -2862,14 +3314,14 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-runtime@npm:^0.2.11": - version: 0.2.11 - resolution: "@napi-rs/wasm-runtime@npm:0.2.11" +"@napi-rs/wasm-runtime@npm:^1.1.0": + version: 1.1.1 + resolution: "@napi-rs/wasm-runtime@npm:1.1.1" dependencies: - "@emnapi/core": "npm:^1.4.3" - "@emnapi/runtime": "npm:^1.4.3" - "@tybys/wasm-util": "npm:^0.9.0" - checksum: 10c0/049bd14c58b99fbe0967b95e9921c5503df196b59be22948d2155f17652eb305cff6728efd8685338b855da7e476dd2551fbe3a313fc2d810938f0717478441e + "@emnapi/core": "npm:^1.7.1" + "@emnapi/runtime": "npm:^1.7.1" + "@tybys/wasm-util": "npm:^0.10.1" + checksum: 10c0/04d57b67e80736e41fe44674a011878db0a8ad893f4d44abb9d3608debb7c174224cba2796ed5b0c1d367368159f3ca6be45f1c59222f70e32ddc880f803d447 languageName: node linkType: hard @@ -3042,235 +3494,144 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.203.0": - version: 0.203.0 - resolution: "@opentelemetry/api-logs@npm:0.203.0" - dependencies: - "@opentelemetry/api": "npm:^1.3.0" - checksum: 10c0/e7a0a0ff46aaeb62192a99f45ef4889222e4fea09be25cab6fea811afc2df95c02ea050b2c98dfc0fc5a6ec6a623d87096af2751fdf91ddbb3afcab61b5325da +"@oxc-resolver/binding-android-arm-eabi@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-android-arm-eabi@npm:11.16.2" + conditions: os=android & cpu=arm languageName: node linkType: hard -"@opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.4.0": - version: 1.9.0 - resolution: "@opentelemetry/api@npm:1.9.0" - checksum: 10c0/9aae2fe6e8a3a3eeb6c1fdef78e1939cf05a0f37f8a4fae4d6bf2e09eb1e06f966ece85805626e01ba5fab48072b94f19b835449e58b6d26720ee19a58298add +"@oxc-resolver/binding-android-arm64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-android-arm64@npm:11.16.2" + conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@opentelemetry/core@npm:2.0.1, @opentelemetry/core@npm:^2.0.0": - version: 2.0.1 - resolution: "@opentelemetry/core@npm:2.0.1" - dependencies: - "@opentelemetry/semantic-conventions": "npm:^1.29.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10c0/d587b1289559757d80da98039f9f57612f84f72ec608cd665dc467c7c6c5ce3a987dfcc2c63b521c7c86ce984a2552b3ead15a0dc458de1cf6bde5cdfe4ca9d8 - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-http@npm:^0.203.0": - version: 0.203.0 - resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.203.0" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.203.0" - "@opentelemetry/otlp-transformer": "npm:0.203.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/21a65ebc40dcab05cf11178e5037f96847ce344c4a855aac46dcab3f74982016318ee75fafdfeeb42f10b92a0a781b7cd8b2b5b036cbe53c14714fd13940142e - languageName: node - linkType: hard - -"@opentelemetry/otlp-exporter-base@npm:0.203.0": - version: 0.203.0 - resolution: "@opentelemetry/otlp-exporter-base@npm:0.203.0" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-transformer": "npm:0.203.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/ad5b771b06b192f06f332f60701d1ad208df88a05975b16e1cdd1dff8e1cb66e775b3e9de513c2f5d48f390f25ca35411ead08ce4849c8203b86a264d34561d3 - languageName: node - linkType: hard - -"@opentelemetry/otlp-transformer@npm:0.203.0": - version: 0.203.0 - resolution: "@opentelemetry/otlp-transformer@npm:0.203.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.203.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-logs": "npm:0.203.0" - "@opentelemetry/sdk-metrics": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - protobufjs: "npm:^7.3.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/3f7b4bfe4bcab4db434ff2c4e59b53de53642d379b80056610456d8e9ae0cbab0f8b69f088078637b7b5ceffd0ac2fda68469c5f295b1c0ac625f522f640338c - languageName: node - linkType: hard - -"@opentelemetry/resources@npm:2.0.1, @opentelemetry/resources@npm:^2.0.0": - version: 2.0.1 - resolution: "@opentelemetry/resources@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/semantic-conventions": "npm:^1.29.0" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10c0/96532b7553b26607a7a892d72f6b03ad12bd542dc23c95135a8ae40362da9c883c21a4cff3d2296d9e0e9bd899a5977e325ed52d83142621a8ffe81d08d99341 - languageName: node - linkType: hard - -"@opentelemetry/sdk-logs@npm:0.203.0": - version: 0.203.0 - resolution: "@opentelemetry/sdk-logs@npm:0.203.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.203.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.4.0 <1.10.0" - checksum: 10c0/02dd9d9969628f05f71ae1d149f1aa6d1fee2dad607923a68a1cfc923e94b046dcc0e18e85e865324e3bda0cee7a5a0ba9fa0d57e4e95fa672be103e2ce60270 - languageName: node - linkType: hard - -"@opentelemetry/sdk-metrics@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/sdk-metrics@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.9.0 <1.10.0" - checksum: 10c0/fcf7ae23d459e5da7cb6fe150064b6dc4e11e47925b08980c3b357bd5534ad388898bbacd0ff8befef6801f43b35142dc7123f028ffde2d0fe2bd72177d07639 - languageName: node - linkType: hard - -"@opentelemetry/sdk-trace-base@npm:2.0.1, @opentelemetry/sdk-trace-base@npm:^2.0.0": - version: 2.0.1 - resolution: "@opentelemetry/sdk-trace-base@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/semantic-conventions": "npm:^1.29.0" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10c0/4e3c733296012b758d007e9c0d8a5b175edbe9a680c73ec75303476e7982b73ad4209f1a2791c1a94c428e5a53eba6c2a72faa430c70336005aa58744d6cb37b - languageName: node - linkType: hard - -"@opentelemetry/sdk-trace-web@npm:^2.0.0": - version: 2.0.1 - resolution: "@opentelemetry/sdk-trace-web@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10c0/48821b91430e24378b0b5b2632e78efdd018a3f840462a6aeba6ce318a6480bad2f623cc7f7f625a9266028ad44b78eb8456181778de6cb18725f26c44e2729b - languageName: node - linkType: hard - -"@opentelemetry/semantic-conventions@npm:^1.25.1, @opentelemetry/semantic-conventions@npm:^1.29.0": - version: 1.36.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.36.0" - checksum: 10c0/edc8a6fe3ec4fc0c67ba3a92b86fb3dcc78fe1eb4f19838d8013c3232b9868540a034dd25cfe0afdd5eae752c5f0e9f42272ff46da144a2d5b35c644478e1c62 - languageName: node - linkType: hard - -"@oxc-resolver/binding-darwin-arm64@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-darwin-arm64@npm:11.3.0" +"@oxc-resolver/binding-darwin-arm64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-darwin-arm64@npm:11.16.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@oxc-resolver/binding-darwin-x64@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-darwin-x64@npm:11.3.0" +"@oxc-resolver/binding-darwin-x64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-darwin-x64@npm:11.16.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@oxc-resolver/binding-freebsd-x64@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-freebsd-x64@npm:11.3.0" +"@oxc-resolver/binding-freebsd-x64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-freebsd-x64@npm:11.16.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.3.0" +"@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.16.2" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@oxc-resolver/binding-linux-arm64-gnu@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-linux-arm64-gnu@npm:11.3.0" +"@oxc-resolver/binding-linux-arm-musleabihf@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm-musleabihf@npm:11.16.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm64-gnu@npm:11.16.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@oxc-resolver/binding-linux-arm64-musl@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-linux-arm64-musl@npm:11.3.0" +"@oxc-resolver/binding-linux-arm64-musl@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm64-musl@npm:11.16.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@oxc-resolver/binding-linux-riscv64-gnu@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-linux-riscv64-gnu@npm:11.3.0" +"@oxc-resolver/binding-linux-ppc64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-ppc64-gnu@npm:11.16.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-riscv64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-riscv64-gnu@npm:11.16.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@oxc-resolver/binding-linux-s390x-gnu@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-linux-s390x-gnu@npm:11.3.0" +"@oxc-resolver/binding-linux-riscv64-musl@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-riscv64-musl@npm:11.16.2" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-s390x-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-s390x-gnu@npm:11.16.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@oxc-resolver/binding-linux-x64-gnu@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-linux-x64-gnu@npm:11.3.0" +"@oxc-resolver/binding-linux-x64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-x64-gnu@npm:11.16.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@oxc-resolver/binding-linux-x64-musl@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-linux-x64-musl@npm:11.3.0" +"@oxc-resolver/binding-linux-x64-musl@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-x64-musl@npm:11.16.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@oxc-resolver/binding-wasm32-wasi@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-wasm32-wasi@npm:11.3.0" +"@oxc-resolver/binding-openharmony-arm64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-openharmony-arm64@npm:11.16.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-wasm32-wasi@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-wasm32-wasi@npm:11.16.2" dependencies: - "@napi-rs/wasm-runtime": "npm:^0.2.11" + "@napi-rs/wasm-runtime": "npm:^1.1.0" conditions: cpu=wasm32 languageName: node linkType: hard -"@oxc-resolver/binding-win32-arm64-msvc@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-win32-arm64-msvc@npm:11.3.0" +"@oxc-resolver/binding-win32-arm64-msvc@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-win32-arm64-msvc@npm:11.16.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@oxc-resolver/binding-win32-x64-msvc@npm:11.3.0": - version: 11.3.0 - resolution: "@oxc-resolver/binding-win32-x64-msvc@npm:11.3.0" +"@oxc-resolver/binding-win32-ia32-msvc@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-win32-ia32-msvc@npm:11.16.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-x64-msvc@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-win32-x64-msvc@npm:11.16.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -3437,79 +3798,6 @@ __metadata: languageName: node linkType: hard -"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": - version: 1.1.2 - resolution: "@protobufjs/aspromise@npm:1.1.2" - checksum: 10c0/a83343a468ff5b5ec6bff36fd788a64c839e48a07ff9f4f813564f58caf44d011cd6504ed2147bf34835bd7a7dd2107052af755961c6b098fd8902b4f6500d0f - languageName: node - linkType: hard - -"@protobufjs/base64@npm:^1.1.2": - version: 1.1.2 - resolution: "@protobufjs/base64@npm:1.1.2" - checksum: 10c0/eec925e681081af190b8ee231f9bad3101e189abbc182ff279da6b531e7dbd2a56f1f306f37a80b1be9e00aa2d271690d08dcc5f326f71c9eed8546675c8caf6 - languageName: node - linkType: hard - -"@protobufjs/codegen@npm:^2.0.4": - version: 2.0.4 - resolution: "@protobufjs/codegen@npm:2.0.4" - checksum: 10c0/26ae337c5659e41f091606d16465bbcc1df1f37cc1ed462438b1f67be0c1e28dfb2ca9f294f39100c52161aef82edf758c95d6d75650a1ddf31f7ddee1440b43 - languageName: node - linkType: hard - -"@protobufjs/eventemitter@npm:^1.1.0": - version: 1.1.0 - resolution: "@protobufjs/eventemitter@npm:1.1.0" - checksum: 10c0/1eb0a75180e5206d1033e4138212a8c7089a3d418c6dfa5a6ce42e593a4ae2e5892c4ef7421f38092badba4040ea6a45f0928869989411001d8c1018ea9a6e70 - languageName: node - linkType: hard - -"@protobufjs/fetch@npm:^1.1.0": - version: 1.1.0 - resolution: "@protobufjs/fetch@npm:1.1.0" - dependencies: - "@protobufjs/aspromise": "npm:^1.1.1" - "@protobufjs/inquire": "npm:^1.1.0" - checksum: 10c0/cda6a3dc2d50a182c5865b160f72077aac197046600091dbb005dd0a66db9cce3c5eaed6d470ac8ed49d7bcbeef6ee5f0bc288db5ff9a70cbd003e5909065233 - languageName: node - linkType: hard - -"@protobufjs/float@npm:^1.0.2": - version: 1.0.2 - resolution: "@protobufjs/float@npm:1.0.2" - checksum: 10c0/18f2bdede76ffcf0170708af15c9c9db6259b771e6b84c51b06df34a9c339dbbeec267d14ce0bddd20acc142b1d980d983d31434398df7f98eb0c94a0eb79069 - languageName: node - linkType: hard - -"@protobufjs/inquire@npm:^1.1.0": - version: 1.1.0 - resolution: "@protobufjs/inquire@npm:1.1.0" - checksum: 10c0/64372482efcba1fb4d166a2664a6395fa978b557803857c9c03500e0ac1013eb4b1aacc9ed851dd5fc22f81583670b4f4431bae186f3373fedcfde863ef5921a - languageName: node - linkType: hard - -"@protobufjs/path@npm:^1.1.2": - version: 1.1.2 - resolution: "@protobufjs/path@npm:1.1.2" - checksum: 10c0/cece0a938e7f5dfd2fa03f8c14f2f1cf8b0d6e13ac7326ff4c96ea311effd5fb7ae0bba754fbf505312af2e38500250c90e68506b97c02360a43793d88a0d8b4 - languageName: node - linkType: hard - -"@protobufjs/pool@npm:^1.1.0": - version: 1.1.0 - resolution: "@protobufjs/pool@npm:1.1.0" - checksum: 10c0/eda2718b7f222ac6e6ad36f758a92ef90d26526026a19f4f17f668f45e0306a5bd734def3f48f51f8134ae0978b6262a5c517c08b115a551756d1a3aadfcf038 - languageName: node - linkType: hard - -"@protobufjs/utf8@npm:^1.1.0": - version: 1.1.0 - resolution: "@protobufjs/utf8@npm:1.1.0" - checksum: 10c0/a3fe31fe3fa29aa3349e2e04ee13dc170cc6af7c23d92ad49e3eeaf79b9766264544d3da824dba93b7855bd6a2982fb40032ef40693da98a136d835752beb487 - languageName: node - linkType: hard - "@radix-ui/number@npm:1.1.1": version: 1.1.1 resolution: "@radix-ui/number@npm:1.1.1" @@ -3524,10 +3812,10 @@ __metadata: languageName: node linkType: hard -"@radix-ui/primitive@npm:1.1.2": - version: 1.1.2 - resolution: "@radix-ui/primitive@npm:1.1.2" - checksum: 10c0/5e2d2528d2fe37c16865e77b0beaac2b415a817ad13d8178db6e8187b2a092672568a64ee0041510abfde3034490a5cadd3057049bb15789020c06892047597c +"@radix-ui/primitive@npm:1.1.3": + version: 1.1.3 + resolution: "@radix-ui/primitive@npm:1.1.3" + checksum: 10c0/88860165ee7066fa2c179f32ffcd3ee6d527d9dcdc0e8be85e9cb0e2c84834be8e3c1a976c74ba44b193f709544e12f54455d892b28e32f0708d89deda6b9f1d languageName: node linkType: hard @@ -3550,6 +3838,25 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-arrow@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-arrow@npm:1.1.7" + dependencies: + "@radix-ui/react-primitive": "npm:2.1.3" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/c3b46766238b3ee2a394d8806a5141432361bf1425110c9f0dcf480bda4ebd304453a53f294b5399c6ee3ccfcae6fd544921fd01ddc379cf5942acdd7168664b + languageName: node + linkType: hard + "@radix-ui/react-collection@npm:1.1.1": version: 1.1.1 resolution: "@radix-ui/react-collection@npm:1.1.1" @@ -3620,16 +3927,16 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-context-menu@npm:^2.2.1": - version: 2.2.4 - resolution: "@radix-ui/react-context-menu@npm:2.2.4" +"@radix-ui/react-context-menu@npm:^2.2.16": + version: 2.2.16 + resolution: "@radix-ui/react-context-menu@npm:2.2.16" dependencies: - "@radix-ui/primitive": "npm:1.1.1" - "@radix-ui/react-context": "npm:1.1.1" - "@radix-ui/react-menu": "npm:2.1.4" - "@radix-ui/react-primitive": "npm:2.0.1" - "@radix-ui/react-use-callback-ref": "npm:1.1.0" - "@radix-ui/react-use-controllable-state": "npm:1.1.0" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-menu": "npm:2.1.16" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" peerDependencies: "@types/react": "*" "@types/react-dom": "*" @@ -3640,7 +3947,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10c0/f500590b1300dfcd8a2d0fb51fcada0e7d9a1a354ac239328ffdd32f3736bde888ebf0cd64d9039f7d894e3d13eb549a872359669de8c7ff128ee1afb9cf21a8 + checksum: 10c0/950f7559e65474a19145238cf44d744cb1e49be2221ff18436ba49b496b05ccf93bd3906aaa2c7ab76bc77daf694911a78442801e0053f57d2e57ebbfd281c49 languageName: node linkType: hard @@ -3671,18 +3978,18 @@ __metadata: linkType: hard "@radix-ui/react-dialog@npm:^1.0.4, @radix-ui/react-dialog@npm:^1.1.1": - version: 1.1.14 - resolution: "@radix-ui/react-dialog@npm:1.1.14" + version: 1.1.15 + resolution: "@radix-ui/react-dialog@npm:1.1.15" dependencies: - "@radix-ui/primitive": "npm:1.1.2" + "@radix-ui/primitive": "npm:1.1.3" "@radix-ui/react-compose-refs": "npm:1.1.2" "@radix-ui/react-context": "npm:1.1.2" - "@radix-ui/react-dismissable-layer": "npm:1.1.10" - "@radix-ui/react-focus-guards": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.11" + "@radix-ui/react-focus-guards": "npm:1.1.3" "@radix-ui/react-focus-scope": "npm:1.1.7" "@radix-ui/react-id": "npm:1.1.1" "@radix-ui/react-portal": "npm:1.1.9" - "@radix-ui/react-presence": "npm:1.1.4" + "@radix-ui/react-presence": "npm:1.1.5" "@radix-ui/react-primitive": "npm:2.1.3" "@radix-ui/react-slot": "npm:1.2.3" "@radix-ui/react-use-controllable-state": "npm:1.2.2" @@ -3698,7 +4005,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10c0/ab7bc783510ed8fccfe91020b214f4a571d5a1d46d398faa33f4c151bc9f586c47483b307e72b67687b06694c194b3aa80dd1de728460fa765db9f3057690ba3 + checksum: 10c0/2f2c88e3c281acaea2fd9b96fa82132d59177d3aa5da2e7c045596fd4028e84e44ac52ac28f4f236910605dd7d9338c2858ba44a9ced2af2e3e523abbfd33014 languageName: node linkType: hard @@ -3728,11 +4035,11 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-dismissable-layer@npm:1.1.10": - version: 1.1.10 - resolution: "@radix-ui/react-dismissable-layer@npm:1.1.10" +"@radix-ui/react-dismissable-layer@npm:1.1.11": + version: 1.1.11 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.11" dependencies: - "@radix-ui/primitive": "npm:1.1.2" + "@radix-ui/primitive": "npm:1.1.3" "@radix-ui/react-compose-refs": "npm:1.1.2" "@radix-ui/react-primitive": "npm:2.1.3" "@radix-ui/react-use-callback-ref": "npm:1.1.1" @@ -3747,7 +4054,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10c0/21a2d03689f5e06586135b6a735937ef14f2571fdf6044a3019bc3f9fa368a9400b5a9b631f43e8ad3682693449e369ffa7cc8642764246ce18ebe7359a45faf + checksum: 10c0/c825572a64073c4d3853702029979f6658770ffd6a98eabc4984e1dee1b226b4078a2a4dc7003f96475b438985e9b21a58e75f51db74dd06848dcae1f2d395dc languageName: node linkType: hard @@ -3812,16 +4119,16 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-focus-guards@npm:1.1.2": - version: 1.1.2 - resolution: "@radix-ui/react-focus-guards@npm:1.1.2" +"@radix-ui/react-focus-guards@npm:1.1.3": + version: 1.1.3 + resolution: "@radix-ui/react-focus-guards@npm:1.1.3" peerDependencies: "@types/react": "*" react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/8d6fa55752b9b6e55d1eebb643178e38a824e8ba418eb29031b2979077a12c4e3922892de9f984dd326f77071a14960cd81e99a960beea07598b8c80da618dc5 + checksum: 10c0/0bab65eb8d7e4f72f685d63de7fbba2450e3cb15ad6a20a16b42195e9d335c576356f5a47cb58d1ffc115393e46d7b14b12c5d4b10029b0ec090861255866985 languageName: node linkType: hard @@ -3940,6 +4247,42 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-menu@npm:2.1.16": + version: 2.1.16 + resolution: "@radix-ui/react-menu@npm:2.1.16" + dependencies: + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-collection": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-direction": "npm:1.1.1" + "@radix-ui/react-dismissable-layer": "npm:1.1.11" + "@radix-ui/react-focus-guards": "npm:1.1.3" + "@radix-ui/react-focus-scope": "npm:1.1.7" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-popper": "npm:1.2.8" + "@radix-ui/react-portal": "npm:1.1.9" + "@radix-ui/react-presence": "npm:1.1.5" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-roving-focus": "npm:1.1.11" + "@radix-ui/react-slot": "npm:1.2.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + aria-hidden: "npm:^1.2.4" + react-remove-scroll: "npm:^2.6.3" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/27516b2b987fa9181c4da8645000af8f60691866a349d7a46b9505fa7d2e9d92b9e364db4f7305d08e9e57d0e1afc8df8354f8ee3c12aa05c0100c16b0e76c27 + languageName: node + linkType: hard + "@radix-ui/react-menu@npm:2.1.4": version: 2.1.4 resolution: "@radix-ui/react-menu@npm:2.1.4" @@ -4004,6 +4347,34 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-popper@npm:1.2.8": + version: 1.2.8 + resolution: "@radix-ui/react-popper@npm:1.2.8" + dependencies: + "@floating-ui/react-dom": "npm:^2.0.0" + "@radix-ui/react-arrow": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + "@radix-ui/react-use-rect": "npm:1.1.1" + "@radix-ui/react-use-size": "npm:1.1.1" + "@radix-ui/rect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/48e3f13eac3b8c13aca8ded37d74db17e1bb294da8d69f142ab6b8719a06c3f90051668bed64520bf9f3abdd77b382ce7ce209d056bb56137cecc949b69b421c + languageName: node + linkType: hard + "@radix-ui/react-portal@npm:1.1.3": version: 1.1.3 resolution: "@radix-ui/react-portal@npm:1.1.3" @@ -4064,9 +4435,9 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-presence@npm:1.1.4": - version: 1.1.4 - resolution: "@radix-ui/react-presence@npm:1.1.4" +"@radix-ui/react-presence@npm:1.1.5": + version: 1.1.5 + resolution: "@radix-ui/react-presence@npm:1.1.5" dependencies: "@radix-ui/react-compose-refs": "npm:1.1.2" "@radix-ui/react-use-layout-effect": "npm:1.1.1" @@ -4080,7 +4451,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10c0/8202647139d6f5097b0abcc43dfba471c00b69da95ca336afe3ea23a165e05ca21992f40fc801760fe442f3e064e54e2f2cbcb9ad758c4b07ef6c69a5b6777bd + checksum: 10c0/d0e61d314250eeaef5369983cb790701d667f51734bafd98cf759072755562018052c594e6cdc5389789f4543cb0a4d98f03ff4e8f37338d6b5bf51a1700c1d1 languageName: node linkType: hard @@ -4122,6 +4493,25 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-primitive@npm:2.1.4": + version: 2.1.4 + resolution: "@radix-ui/react-primitive@npm:2.1.4" + dependencies: + "@radix-ui/react-slot": "npm:1.2.4" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/90d687b222a25975371ed1f9f08648d75237214b8dec4cbaf09ec9ac951339b17421278f1aff2fb7c5672ba8bd03774a94904efdba73805dd5cc947ce5be8c4a + languageName: node + linkType: hard + "@radix-ui/react-progress@npm:^1.1.0": version: 1.1.1 resolution: "@radix-ui/react-progress@npm:1.1.1" @@ -4169,6 +4559,33 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-roving-focus@npm:1.1.11": + version: 1.1.11 + resolution: "@radix-ui/react-roving-focus@npm:1.1.11" + dependencies: + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-collection": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-direction": "npm:1.1.1" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/2cd43339c36e89a3bf1db8aab34b939113dfbde56bf3a33df2d74757c78c9489b847b1962f1e2441c67e41817d120cb6177943e0f655f47bc1ff8e44fd55b1a2 + languageName: node + linkType: hard + "@radix-ui/react-separator@npm:^1.1.0": version: 1.1.1 resolution: "@radix-ui/react-separator@npm:1.1.1" @@ -4189,11 +4606,11 @@ __metadata: linkType: hard "@radix-ui/react-slider@npm:^1.1.2": - version: 1.3.5 - resolution: "@radix-ui/react-slider@npm:1.3.5" + version: 1.3.6 + resolution: "@radix-ui/react-slider@npm:1.3.6" dependencies: "@radix-ui/number": "npm:1.1.1" - "@radix-ui/primitive": "npm:1.1.2" + "@radix-ui/primitive": "npm:1.1.3" "@radix-ui/react-collection": "npm:1.1.7" "@radix-ui/react-compose-refs": "npm:1.1.2" "@radix-ui/react-context": "npm:1.1.2" @@ -4213,7 +4630,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10c0/2f5f37f953d78ac02ed74120afe76badf3a7d0e19036f4de6cdeda38220718a1d5113ffc2f43e0b3de73e14564cae9a09d1968ee3f9641625849d126a036717f + checksum: 10c0/a53d7854e28c5ef3d29b76c8d04cc3c723b982b643152cd5a8fefc7a8359180f8fd21753e5a08302a290bc837e7be04f2efad9d308b7a4a23326df6a6b1ac882 languageName: node linkType: hard @@ -4247,6 +4664,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-slot@npm:1.2.4": + version: 1.2.4 + resolution: "@radix-ui/react-slot@npm:1.2.4" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/8b719bb934f1ae5ac0e37214783085c17c2f1080217caf514c1c6cc3d9ca56c7e19d25470b26da79aa6e605ab36589edaade149b76f5fc0666f1063e2fc0a0dc + languageName: node + linkType: hard + "@radix-ui/react-use-callback-ref@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-use-callback-ref@npm:1.1.0" @@ -4403,6 +4835,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-rect@npm:1.1.1" + dependencies: + "@radix-ui/rect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/271711404c05c589c8dbdaa748749e7daf44bcc6bffc9ecd910821c3ebca0ee245616cf5b39653ce690f53f875c3836fd3f36f51ab1c628273b6db599eee4864 + languageName: node + linkType: hard + "@radix-ui/react-use-size@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-use-size@npm:1.1.0" @@ -4434,10 +4881,10 @@ __metadata: linkType: hard "@radix-ui/react-visually-hidden@npm:^1.0.3": - version: 1.2.3 - resolution: "@radix-ui/react-visually-hidden@npm:1.2.3" + version: 1.2.4 + resolution: "@radix-ui/react-visually-hidden@npm:1.2.4" dependencies: - "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-primitive": "npm:2.1.4" peerDependencies: "@types/react": "*" "@types/react-dom": "*" @@ -4448,7 +4895,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10c0/cf86a37f1cbee50a964056f3dc4f6bb1ee79c76daa321f913aa20ff3e1ccdfafbf2b114d7bb616aeefc7c4b895e6ca898523fdb67710d89bd5d8edb739a0d9b6 + checksum: 10c0/cca313cd3268f483612da1ab91c4cca55a54d24963dd543154f2d043bfdca21a96ab0582152ae473de44769474867d5433dbadae799a42932e6204fd2d5fa889 languageName: node linkType: hard @@ -4459,69 +4906,76 @@ __metadata: languageName: node linkType: hard -"@react-spring/animated@npm:~10.0.1": - version: 10.0.1 - resolution: "@react-spring/animated@npm:10.0.1" +"@radix-ui/rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/rect@npm:1.1.1" + checksum: 10c0/0dac4f0f15691199abe6a0e067821ddd9d0349c0c05f39834e4eafc8403caf724106884035ae91bbc826e10367e6a5672e7bec4d4243860fa7649de246b1f60b + languageName: node + linkType: hard + +"@react-spring/animated@npm:~10.0.3": + version: 10.0.3 + resolution: "@react-spring/animated@npm:10.0.3" dependencies: - "@react-spring/shared": "npm:~10.0.1" - "@react-spring/types": "npm:~10.0.1" + "@react-spring/shared": "npm:~10.0.3" + "@react-spring/types": "npm:~10.0.3" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/aaccd4a8b0280ac846d463b253ad8f092ee4afc9dbedc8e77616adf5399ffec755344f09fdd8487cadaf815840dff84d354d1143579c27c2fcd6937549b5fc40 + checksum: 10c0/6142522f310926729a92009a108ec1bd0d1fe3dca7e9aa0c49692c82fbed28f70c0ea9808fc7452f04bb688ae34333e780f794495906fa4b0efbfc7d53a19b6b languageName: node linkType: hard -"@react-spring/core@npm:~10.0.1": - version: 10.0.1 - resolution: "@react-spring/core@npm:10.0.1" +"@react-spring/core@npm:~10.0.3": + version: 10.0.3 + resolution: "@react-spring/core@npm:10.0.3" dependencies: - "@react-spring/animated": "npm:~10.0.1" - "@react-spring/shared": "npm:~10.0.1" - "@react-spring/types": "npm:~10.0.1" + "@react-spring/animated": "npm:~10.0.3" + "@react-spring/shared": "npm:~10.0.3" + "@react-spring/types": "npm:~10.0.3" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/059b122dda4138e5e7e461abd49350921e326735ca9a1d8aa19b1fdbae0937661b5f71af6fe82fd8f59e8db5549627849b38cc3f7ef2ec7ee9c93c3d6225174f + checksum: 10c0/d941541d4a40a5229f488e78b414149d54238065178fdd14db307a851d285d521ab1914c0d426b102e0190651dbe752aeb743cee4cd497f5c066be937f2d1790 languageName: node linkType: hard -"@react-spring/rafz@npm:~10.0.1": - version: 10.0.1 - resolution: "@react-spring/rafz@npm:10.0.1" - checksum: 10c0/cba76f143d3a06f79dd0c09f7aefd17df9cca9b2c1ef7f9103255e5351326f4a42a5a1366f731a78f74380d96ba683bcc2a49312ed1e4b9e9e249e72c9ff68cb +"@react-spring/rafz@npm:~10.0.3": + version: 10.0.3 + resolution: "@react-spring/rafz@npm:10.0.3" + checksum: 10c0/4cf6f710e2be64a3d94e90a20a24a93c68a89b618538d32aaf0079217f9fbed610395051a181d2d010c7ed6898cb7239a3f2ced1d91dd93e4138563ffd2d44ce languageName: node linkType: hard -"@react-spring/shared@npm:~10.0.1": - version: 10.0.1 - resolution: "@react-spring/shared@npm:10.0.1" +"@react-spring/shared@npm:~10.0.3": + version: 10.0.3 + resolution: "@react-spring/shared@npm:10.0.3" dependencies: - "@react-spring/rafz": "npm:~10.0.1" - "@react-spring/types": "npm:~10.0.1" + "@react-spring/rafz": "npm:~10.0.3" + "@react-spring/types": "npm:~10.0.3" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/f056aaa018b3744afd8244e8eea24534d32f92fad9ace815b80e159b296fb5db148e2c9bd840ad9a5188e7a3c0778fd564b8af9ae02cd500e019a228398fb3cf + checksum: 10c0/d8b33b2390792924d0ff2b57b3098d1c6b688a788fc67e83b168928659aad7435dd2c399925a765ffa5080182634dde8f9f76c919f2c259a33af15319187f72f languageName: node linkType: hard -"@react-spring/types@npm:~10.0.1": - version: 10.0.1 - resolution: "@react-spring/types@npm:10.0.1" - checksum: 10c0/260890f9c156dc69b77c846510017156d8c0a07cce70edc7c108e57b0cf4122b26a15e724b191481a51b2c914296de9e81d56618b2c339339d4b221930691baa +"@react-spring/types@npm:~10.0.3": + version: 10.0.3 + resolution: "@react-spring/types@npm:10.0.3" + checksum: 10c0/f9bc2619dc9997fe93ebab90fd98118106e53bc45c9c279caaa7081b69d3ed0186d603d5ed445e56bb8ad0075553d15908a2a54e3a0a36ef7cd43a03c1650b02 languageName: node linkType: hard "@react-spring/web@npm:^10.0.0": - version: 10.0.1 - resolution: "@react-spring/web@npm:10.0.1" + version: 10.0.3 + resolution: "@react-spring/web@npm:10.0.3" dependencies: - "@react-spring/animated": "npm:~10.0.1" - "@react-spring/core": "npm:~10.0.1" - "@react-spring/shared": "npm:~10.0.1" - "@react-spring/types": "npm:~10.0.1" + "@react-spring/animated": "npm:~10.0.3" + "@react-spring/core": "npm:~10.0.3" + "@react-spring/shared": "npm:~10.0.3" + "@react-spring/types": "npm:~10.0.3" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/a0c788c9fd881ccb834feb22fc0694e74e59f7b76e498f0096f5b65e2c9812513955bf45ee27d7c5348f56a7bba7c5a6961d4be663728bb2172fe5aa6b6bdfc4 + checksum: 10c0/cafbf55991d68920e94419b5b081cfb0aea2ddfa193e984861ab53306365bf81152bce2861370f9b26d4027dd714e8acc0dfbd57bd2eda99e827c3212e22a51c languageName: node linkType: hard @@ -4558,7 +5012,7 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.0.1": +"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.2.0": version: 5.3.0 resolution: "@rollup/pluginutils@npm:5.3.0" dependencies: @@ -4574,22 +5028,6 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.1.3": - version: 5.1.3 - resolution: "@rollup/pluginutils@npm:5.1.3" - dependencies: - "@types/estree": "npm:^1.0.0" - estree-walker: "npm:^2.0.2" - picomatch: "npm:^4.0.2" - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - checksum: 10c0/ba46ad588733fb01d184ee3bc7a127d626158bc840b5874a94c129ff62689d12f16f537530709c54da6f3b71f67d705c4e09235b1dc9542e9d47ee8f2d0b8b9e - languageName: node - linkType: hard - "@rollup/rollup-android-arm-eabi@npm:4.50.1": version: 4.50.1 resolution: "@rollup/rollup-android-arm-eabi@npm:4.50.1" @@ -5094,39 +5532,38 @@ __metadata: linkType: hard "@testing-library/dom@npm:^10.1.0": - version: 10.4.0 - resolution: "@testing-library/dom@npm:10.4.0" + version: 10.4.1 + resolution: "@testing-library/dom@npm:10.4.1" dependencies: "@babel/code-frame": "npm:^7.10.4" "@babel/runtime": "npm:^7.12.5" "@types/aria-query": "npm:^5.0.1" aria-query: "npm:5.3.0" - chalk: "npm:^4.1.0" dom-accessibility-api: "npm:^0.5.9" lz-string: "npm:^1.5.0" + picocolors: "npm:1.1.1" pretty-format: "npm:^27.0.2" - checksum: 10c0/0352487720ecd433400671e773df0b84b8268fb3fe8e527cdfd7c11b1365b398b4e0eddba6e7e0c85e8d615f48257753283fccec41f6b986fd6c85f15eb5f84f + checksum: 10c0/19ce048012d395ad0468b0dbcc4d0911f6f9e39464d7a8464a587b29707eed5482000dad728f5acc4ed314d2f4d54f34982999a114d2404f36d048278db815b1 languageName: node linkType: hard "@testing-library/jest-dom@npm:^6.6.3": - version: 6.6.4 - resolution: "@testing-library/jest-dom@npm:6.6.4" + version: 6.9.1 + resolution: "@testing-library/jest-dom@npm:6.9.1" dependencies: "@adobe/css-tools": "npm:^4.4.0" aria-query: "npm:^5.0.0" css.escape: "npm:^1.5.1" dom-accessibility-api: "npm:^0.6.3" - lodash: "npm:^4.17.21" picocolors: "npm:^1.1.1" redent: "npm:^3.0.0" - checksum: 10c0/cb73adf4910f654f6cc61cfb9a551efdffa04ef423bc7fbfd67a6d8aa31c6c6dc6363fe9db23a35fc7cb32ff1390e6e1c77575c2fa70d8b028a943af32bc214c + checksum: 10c0/4291ebd2f0f38d14cefac142c56c337941775a5807e2a3d6f1a14c2fbd6be76a18e498ed189e95bedc97d9e8cf1738049bc76c85b5bc5e23fae7c9e10f7b3a12 languageName: node linkType: hard "@testing-library/react@npm:^16.0.0": - version: 16.3.0 - resolution: "@testing-library/react@npm:16.3.0" + version: 16.3.1 + resolution: "@testing-library/react@npm:16.3.1" dependencies: "@babel/runtime": "npm:^7.12.5" peerDependencies: @@ -5140,7 +5577,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10c0/3a2cb1f87c9a67e1ebbbcfd99b94b01e496fc35147be8bc5d8bf07a699c7d523a09d57ef2f7b1d91afccd1a28e21eda3b00d80187fbb51b1de01e422592d845e + checksum: 10c0/5a26ceaa4ab1d065be722d93e3b019883864ae038f9fd1c974f5b8a173f5f35a25768ecb2baa02a783299f009cbcd09fa7ee0b8b3d360d1c0f81535436358b28 languageName: node linkType: hard @@ -5153,12 +5590,12 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.9.0": - version: 0.9.0 - resolution: "@tybys/wasm-util@npm:0.9.0" +"@tybys/wasm-util@npm:^0.10.1": + version: 0.10.1 + resolution: "@tybys/wasm-util@npm:0.10.1" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/f9fde5c554455019f33af6c8215f1a1435028803dc2a2825b077d812bed4209a1a64444a4ca0ce2ea7e1175c8d88e2f9173a36a33c199e8a5c671aa31de8242d + checksum: 10c0/b255094f293794c6d2289300c5fbcafbb5532a3aed3a5ffd2f8dc1828e639b88d75f6a376dd8f94347a44813fd7a7149d8463477a9a49525c8b2dcaa38c2d1e8 languageName: node linkType: hard @@ -5233,22 +5670,6 @@ __metadata: languageName: node linkType: hard -"@types/dom-mediacapture-transform@npm:^0.1.11": - version: 0.1.11 - resolution: "@types/dom-mediacapture-transform@npm:0.1.11" - dependencies: - "@types/dom-webcodecs": "npm:*" - checksum: 10c0/19c76d54cf31aa2a925011fc5f973dff9a10bdecfdf2285e5e568e61850a0fa2b8c9f1807a1462cbefd57ec26d32eeaa9c359117aca9d9fe7f0d6f2fff33f51e - languageName: node - linkType: hard - -"@types/dom-webcodecs@npm:*": - version: 0.1.15 - resolution: "@types/dom-webcodecs@npm:0.1.15" - checksum: 10c0/1407f0352156c99c9b4378fb4c0c799b061520d031903a7f359ad09a6f706cc1fd56bafb272bb1a3decffcb32e54a51d2f07442eb72622464a950cff7f9e8862 - languageName: node - linkType: hard - "@types/estree@npm:1.0.8, @types/estree@npm:^1.0.8": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" @@ -5332,21 +5753,21 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=13.7.0": - version: 24.1.0 - resolution: "@types/node@npm:24.1.0" +"@types/node@npm:*": + version: 25.0.3 + resolution: "@types/node@npm:25.0.3" dependencies: - undici-types: "npm:~7.8.0" - checksum: 10c0/6c4686bc144f6ce7bffd4cadc3e1196e2217c1da4c639c637213719c8a3ee58b6c596b994befcbffeacd9d9eb0c3bff6529d2bc27da5d1cb9d58b1da0056f9f4 + undici-types: "npm:~7.16.0" + checksum: 10c0/b7568f0d765d9469621615e2bb257c7fd1953d95e9acbdb58dffb6627a2c4150d405a4600aa1ad8a40182a94fe5f903cafd3c0a2f5132814debd0e3bfd61f835 languageName: node linkType: hard "@types/node@npm:^24.0.0": - version: 24.10.0 - resolution: "@types/node@npm:24.10.0" + version: 24.10.4 + resolution: "@types/node@npm:24.10.4" dependencies: undici-types: "npm:~7.16.0" - checksum: 10c0/f82ed7194e16f5590ef7afdc20c6d09068c76d50278b485ede8f0c5749683536e3064ffa8def8db76915196afb3724b854aa5723c64d6571b890b14492943b46 + checksum: 10c0/069639cb7233ee747df1897b5e784f6b6c5da765c96c94773c580aac888fa1a585048d2a6e95eb8302d89c7a9df75801c8b5a0b7d0221d4249059cf09a5f4228 languageName: node linkType: hard @@ -5358,43 +5779,43 @@ __metadata: linkType: hard "@types/pako@npm:^2.0.3": - version: 2.0.3 - resolution: "@types/pako@npm:2.0.3" - checksum: 10c0/45119ac3c4e8a77317c35493327039b74e333562f06ce038048228918d8ddfaa7958125aab960d1565b3861046022754c414dba1eecb210c44a32c415956bee2 + version: 2.0.4 + resolution: "@types/pako@npm:2.0.4" + checksum: 10c0/5765bf8bc7e77ee141c454118f03e544b8f6cb51eb257d82dc5830feeab8cd00818af3a1eabefdfbe8dd3ae9916ed5403937bf1031a0ee51deea27fdf4dccdfb languageName: node linkType: hard "@types/qrcode@npm:^1.5.5": - version: 1.5.5 - resolution: "@types/qrcode@npm:1.5.5" + version: 1.5.6 + resolution: "@types/qrcode@npm:1.5.6" dependencies: "@types/node": "npm:*" - checksum: 10c0/b8e6709905d1edb32dda414408acab18ac4aefcbe7bf96d9e32ba94218f45b99c8938ba7a09863ce82a67b226195099fd0f48881d16ee844899087b7f249955f + checksum: 10c0/84844ca63e5f32bc47d44dda0f8a6f7cdcc7ce44e7b24f10f19d50796f31d12c058f702a8f7d352c9e82a023a9abc36fa1ad01ddf0a209dd8ed4562ea76481fc languageName: node linkType: hard "@types/react-dom@npm:^19.0.0": - version: 19.1.6 - resolution: "@types/react-dom@npm:19.1.6" + version: 19.2.3 + resolution: "@types/react-dom@npm:19.2.3" peerDependencies: - "@types/react": ^19.0.0 - checksum: 10c0/7ba74eee2919e3f225e898b65fdaa16e54952aaf9e3472a080ddc82ca54585e46e60b3c52018d21d4b7053f09d27b8293e9f468b85f9932ff452cd290cc131e8 + "@types/react": ^19.2.0 + checksum: 10c0/b486ebe0f4e2fb35e2e108df1d8fc0927ca5d6002d5771e8a739de11239fe62d0e207c50886185253c99eb9dedfeeb956ea7429e5ba17f6693c7acb4c02f8cd1 languageName: node linkType: hard "@types/react@npm:^19.0.0": - version: 19.1.8 - resolution: "@types/react@npm:19.1.8" + version: 19.2.7 + resolution: "@types/react@npm:19.2.7" dependencies: - csstype: "npm:^3.0.2" - checksum: 10c0/4908772be6dc941df276931efeb0e781777fa76e4d5d12ff9f75eb2dcc2db3065e0100efde16fde562c5bafa310cc8f50c1ee40a22640459e066e72cd342143e + csstype: "npm:^3.2.2" + checksum: 10c0/a7b75f1f9fcb34badd6f84098be5e35a0aeca614bc91f93d2698664c0b2ba5ad128422bd470ada598238cebe4f9e604a752aead7dc6f5a92261d0c7f9b27cfd1 languageName: node linkType: hard "@types/sdp-transform@npm:^2.4.5": - version: 2.4.10 - resolution: "@types/sdp-transform@npm:2.4.10" - checksum: 10c0/3482950907edc1b841a9d550c391b89b1d56f078bed33e83fb01d2de3097c6045d4627fb50b192712ff34c71784b476dce044c4df735808a9d0bfeca1e77df7d + version: 2.15.0 + resolution: "@types/sdp-transform@npm:2.15.0" + checksum: 10c0/8740a51ef3478dcc560952d815edb650680f924e00fcb19eea74a8422640e9a1e18a63d9dda750576598cb1c15b8bb6a68bd5076a9e9ac170d828cb97fa1ff26 languageName: node linkType: hard @@ -5443,23 +5864,22 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^8.31.0": - version: 8.38.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.38.0" + version: 8.51.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.51.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.38.0" - "@typescript-eslint/type-utils": "npm:8.38.0" - "@typescript-eslint/utils": "npm:8.38.0" - "@typescript-eslint/visitor-keys": "npm:8.38.0" - graphemer: "npm:^1.4.0" + "@typescript-eslint/scope-manager": "npm:8.51.0" + "@typescript-eslint/type-utils": "npm:8.51.0" + "@typescript-eslint/utils": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.2.0" peerDependencies: - "@typescript-eslint/parser": ^8.38.0 + "@typescript-eslint/parser": ^8.51.0 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/199b82e9f0136baecf515df7c31bfed926a7c6d4e6298f64ee1a77c8bdd7a8cb92a2ea55a5a345c9f2948a02f7be6d72530efbe803afa1892b593fbd529d0c27 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/3140e66a0f722338d56bf3de2b7cbb9a74a812d8da90fc61975ea029f6a401252c0824063d4c4baab9827de6f0209b34f4bbdc46e3f5fefd8fa2ff4a3980406f languageName: node linkType: hard @@ -5475,31 +5895,31 @@ __metadata: linkType: hard "@typescript-eslint/parser@npm:^8.31.0": - version: 8.38.0 - resolution: "@typescript-eslint/parser@npm:8.38.0" + version: 8.51.0 + resolution: "@typescript-eslint/parser@npm:8.51.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.38.0" - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/typescript-estree": "npm:8.38.0" - "@typescript-eslint/visitor-keys": "npm:8.38.0" + "@typescript-eslint/scope-manager": "npm:8.51.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/5580c2a328f0c15f85e4a0961a07584013cc0aca85fe868486187f7c92e9e3f6602c6e3dab917b092b94cd492ed40827c6f5fea42730bef88eb17592c947adf4 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/b6aab1d82cc98a77aaae7637bf2934980104799793b3fd5b893065d930fe9b23cd6c2059d6f73fb454ea08f9e956e84fa940310d8435092a14be645a42062d94 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/project-service@npm:8.38.0" +"@typescript-eslint/project-service@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/project-service@npm:8.51.0" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.38.0" - "@typescript-eslint/types": "npm:^8.38.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.51.0" + "@typescript-eslint/types": "npm:^8.51.0" debug: "npm:^4.3.4" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/87d2f55521e289bbcdc666b1f4587ee2d43039cee927310b05abaa534b528dfb1b5565c1545bb4996d7fbdf9d5a3b0aa0e6c93a8f1289e3fcfd60d246364a884 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/c6e6efbf79e126261e1742990b0872a34bbbe9931d99f0aabd12cb70a65a361e02d626db4b632dabee2b2c26b7e5b48344fc5a796c56438ae0788535e2bbe092 languageName: node linkType: hard @@ -5523,38 +5943,38 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/scope-manager@npm:8.38.0" +"@typescript-eslint/scope-manager@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/scope-manager@npm:8.51.0" dependencies: - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/visitor-keys": "npm:8.38.0" - checksum: 10c0/ceaf489ea1f005afb187932a7ee363dfe1e0f7cc3db921283991e20e4c756411a5e25afbec72edd2095d6a4384f73591f4c750cf65b5eaa650c90f64ef9fe809 + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" + checksum: 10c0/dd1e75fc13e6b1119954612d9e8ad3f2d91bc37dcde85fd00e959171aaf6c716c4c265c90c5accf24b5831bd3f48510b0775e5583085b8fa2ad5c37c8980ae1a languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.38.0, @typescript-eslint/tsconfig-utils@npm:^8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.38.0" +"@typescript-eslint/tsconfig-utils@npm:8.51.0, @typescript-eslint/tsconfig-utils@npm:^8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.51.0" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/1a90da16bf1f7cfbd0303640a8ead64a0080f2b1d5969994bdac3b80abfa1177f0c6fbf61250bae082e72cf5014308f2f5cc98edd6510202f13420a7ffd07a84 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/46cab9a5342b4a8f8a1d05aaee4236c5262a540ad0bca1f0e8dad5d63ed1e634b88ce0c82a612976dab09861e21086fc995a368df0435ac43fb960e0b9e5cde2 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/type-utils@npm:8.38.0" +"@typescript-eslint/type-utils@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/type-utils@npm:8.51.0" dependencies: - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/typescript-estree": "npm:8.38.0" - "@typescript-eslint/utils": "npm:8.38.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" + "@typescript-eslint/utils": "npm:8.51.0" debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.2.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/27795c4bd0be395dda3424e57d746639c579b7522af1c17731b915298a6378fd78869e8e141526064b6047db2c86ba06444469ace19c98cda5779d06f4abd37c + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/7c17214e54bc3a4fe4551d9251ffbac52e84ca46eeae840c0f981994b7cbcc837ef32a2b6d510b02d958a8f568df355e724d9c6938a206716271a1b0c00801b7 languageName: node linkType: hard @@ -5572,14 +5992,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.38.0, @typescript-eslint/types@npm:^8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/types@npm:8.38.0" - checksum: 10c0/f0ac0060c98c0f3d1871f107177b6ae25a0f1846ca8bd8cfc7e1f1dd0ddce293cd8ac4a5764d6a767de3503d5d01defcd68c758cb7ba6de52f82b209a918d0d2 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:^8.46.0": +"@typescript-eslint/types@npm:8.51.0, @typescript-eslint/types@npm:^8.46.0, @typescript-eslint/types@npm:^8.51.0": version: 8.51.0 resolution: "@typescript-eslint/types@npm:8.51.0" checksum: 10c0/eb3473d0bb71eb886438f35887b620ffadae7853b281752a40c73158aee644d136adeb82549be7d7c30f346fe888b2e979dff7e30e67b35377e8281018034529 @@ -5622,23 +6035,22 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.38.0" +"@typescript-eslint/typescript-estree@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.51.0" dependencies: - "@typescript-eslint/project-service": "npm:8.38.0" - "@typescript-eslint/tsconfig-utils": "npm:8.38.0" - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/visitor-keys": "npm:8.38.0" + "@typescript-eslint/project-service": "npm:8.51.0" + "@typescript-eslint/tsconfig-utils": "npm:8.51.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.1.0" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.2.0" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/00a00f6549877f4ae5c2847fa5ac52bf42cbd59a87533856c359e2746e448ed150b27a6137c92fd50c06e6a4b39e386d6b738fac97d80d05596e81ce55933230 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/5386acc67298a6757681b6264c29a6b9304be7a188f11498bbaa82bb0a3095fd79394ad80d6520bdff3fa3093199f9a438246604ee3281b76f7ed574b7516854 languageName: node linkType: hard @@ -5660,18 +6072,18 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/utils@npm:8.38.0" +"@typescript-eslint/utils@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/utils@npm:8.51.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.38.0" - "@typescript-eslint/types": "npm:8.38.0" - "@typescript-eslint/typescript-estree": "npm:8.38.0" + "@typescript-eslint/scope-manager": "npm:8.51.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/e97a45bf44f315f9ed8c2988429e18c88e3369c9ee3227ee86446d2d49f7325abebbbc9ce801e178f676baa986d3e1fd4b5391f1640c6eb8944c123423ae43bb + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/ffb8237cfb33a1998ae2812b136d42fb65e7497f185d46097d19e43112e41b3ef59f901ba679c2e5372ad3007026f6e5add3a3de0f2e75ce6896918713fa38a8 languageName: node linkType: hard @@ -5710,13 +6122,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.38.0": - version: 8.38.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.38.0" +"@typescript-eslint/visitor-keys@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.51.0" dependencies: - "@typescript-eslint/types": "npm:8.38.0" + "@typescript-eslint/types": "npm:8.51.0" eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/071a756e383f41a6c9e51d78c8c64bd41cd5af68b0faef5fbaec4fa5dbd65ec9e4cd610c2e2cdbe9e2facc362995f202850622b78e821609a277b5b601a1d4ec + checksum: 10c0/fce5603961cf336e71095f7599157de65e3182f61cbd6cab33a43551ee91485b4e9bf6cacc1b275cf6f3503b92f8568fe2267a45c82e60e386ee73db727a26ca languageName: node linkType: hard @@ -5746,8 +6158,8 @@ __metadata: linkType: hard "@vector-im/compound-design-tokens@npm:^6.0.0": - version: 6.0.0 - resolution: "@vector-im/compound-design-tokens@npm:6.0.0" + version: 6.6.0 + resolution: "@vector-im/compound-design-tokens@npm:6.6.0" peerDependencies: "@types/react": "*" react: ^17 || ^18 || ^19.0.0 @@ -5756,16 +6168,16 @@ __metadata: optional: true react: optional: true - checksum: 10c0/1af5b2b73a3a55149047cd0716f071b83a4df8a210c9ad432db4cc2f9b9e72e958f93ff850dbaddb88e37a01870c5eb810b03dfb0acc89cc147eaaf6cf1dada1 + checksum: 10c0/93b152dd1de96371f9b6b1f7dbcc381d7ab598031dbc900f52d610f015766c0d4426ae6e47d417e723bfb62d1a53099155b4d788848b78232916ba132c03c2fe languageName: node linkType: hard "@vector-im/compound-web@npm:^8.0.0": - version: 8.2.0 - resolution: "@vector-im/compound-web@npm:8.2.0" + version: 8.3.4 + resolution: "@vector-im/compound-web@npm:8.3.4" dependencies: "@floating-ui/react": "npm:^0.27.0" - "@radix-ui/react-context-menu": "npm:^2.2.1" + "@radix-ui/react-context-menu": "npm:^2.2.16" "@radix-ui/react-dropdown-menu": "npm:^2.1.1" "@radix-ui/react-form": "npm:^0.1.0" "@radix-ui/react-progress": "npm:^1.1.0" @@ -5777,12 +6189,12 @@ __metadata: "@fontsource/inconsolata": ^5 "@fontsource/inter": ^5 "@types/react": "*" - "@vector-im/compound-design-tokens": ">=1.6.1 <6.0.0" + "@vector-im/compound-design-tokens": ">=1.6.1 <7.0.0" react: ^18 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/4ac4074dcf9611bdff7de4bf66763397c926d6312f31758bcabe3e7bf704cb76bc2ce1023fe5f2cf0d05e97c9c540fef8b63edea7a521a2f7b4b7fbcb883fb17 + checksum: 10c0/44764fa64b5fce2e7181e25b50ee970eda4d921cf650b92bd5e88df0eb60872f3086b8702d18f55c3e39b3751ac19f10bafda8c4306df65c3605bd44b297d95c languageName: node linkType: hard @@ -6299,21 +6711,20 @@ __metadata: languageName: node linkType: hard -"autoprefixer@npm:^10.4.21": - version: 10.4.21 - resolution: "autoprefixer@npm:10.4.21" +"autoprefixer@npm:^10.4.23": + version: 10.4.23 + resolution: "autoprefixer@npm:10.4.23" dependencies: - browserslist: "npm:^4.24.4" - caniuse-lite: "npm:^1.0.30001702" - fraction.js: "npm:^4.3.7" - normalize-range: "npm:^0.1.2" + browserslist: "npm:^4.28.1" + caniuse-lite: "npm:^1.0.30001760" + fraction.js: "npm:^5.3.4" picocolors: "npm:^1.1.1" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.1.0 bin: autoprefixer: bin/autoprefixer - checksum: 10c0/de5b71d26d0baff4bbfb3d59f7cf7114a6030c9eeb66167acf49a32c5b61c68e308f1e0f869d92334436a221035d08b51cd1b2f2c4689b8d955149423c16d4d4 + checksum: 10c0/3765c5d0fa3e95fb2ebe9d5a6d4da0156f5d346c7ec9ac0fbf5c97c8139d0ca1e8743bf5dc1b4aa954467be6929fddf8498a3b6202d468d70b5f359f3b6af90f languageName: node linkType: hard @@ -6421,6 +6832,15 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.9.0": + version: 2.9.11 + resolution: "baseline-browser-mapping@npm:2.9.11" + bin: + baseline-browser-mapping: dist/cli.js + checksum: 10c0/eba49fcc1b33ab994aeeb73a4848f2670e06a0886dd5b903689ae6f60d47e7f1bea9262dbb2548c48179e858f7eda2b82ddf941ae783b862f4dcc51085a246f2 + languageName: node + linkType: hard + "before-after-hook@npm:^2.2.0": version: 2.2.3 resolution: "before-after-hook@npm:2.2.3" @@ -6636,7 +7056,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.24.0, browserslist@npm:^4.24.3, browserslist@npm:^4.24.4": +"browserslist@npm:^4.24.0, browserslist@npm:^4.24.3": version: 4.24.4 resolution: "browserslist@npm:4.24.4" dependencies: @@ -6650,7 +7070,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.25.0, browserslist@npm:^4.25.1": +"browserslist@npm:^4.25.1": version: 4.25.1 resolution: "browserslist@npm:4.25.1" dependencies: @@ -6664,6 +7084,21 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.28.1": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" + dependencies: + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" + bin: + browserslist: cli.js + checksum: 10c0/545a5fa9d7234e3777a7177ec1e9134bb2ba60a69e6b95683f6982b1473aad347c77c1264ccf2ac5dea609a9731fbfbda6b85782bdca70f80f86e28a402504bd + languageName: node + linkType: hard + "bs58@npm:^6.0.0": version: 6.0.0 resolution: "bs58@npm:6.0.0" @@ -6838,13 +7273,20 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001688, caniuse-lite@npm:^1.0.30001702, caniuse-lite@npm:^1.0.30001726": +"caniuse-lite@npm:^1.0.30001688, caniuse-lite@npm:^1.0.30001726": version: 1.0.30001757 resolution: "caniuse-lite@npm:1.0.30001757" checksum: 10c0/3ccb71fa2bf1f8c96ff1bf9b918b08806fed33307e20a3ce3259155fda131eaf96cfcd88d3d309c8fd7f8285cc71d89a3b93648a1c04814da31c301f98508d42 languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001759, caniuse-lite@npm:^1.0.30001760": + version: 1.0.30001762 + resolution: "caniuse-lite@npm:1.0.30001762" + checksum: 10c0/93707eac5b0240af3f2ce6e2d7ab504a6fefcf9c2f9cd8fb9d488e496a333c61e557dab0472c1b00c17bc386a5dbb792aa4c778cda2d768e17f986617d7aec53 + languageName: node + linkType: hard + "caseless@npm:~0.12.0": version: 0.12.0 resolution: "caseless@npm:0.12.0" @@ -6865,7 +7307,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:~4.1.0": +"chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:~4.1.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -7339,16 +7781,16 @@ __metadata: languageName: node linkType: hard -"css-has-pseudo@npm:^7.0.2": - version: 7.0.2 - resolution: "css-has-pseudo@npm:7.0.2" +"css-has-pseudo@npm:^7.0.3": + version: 7.0.3 + resolution: "css-has-pseudo@npm:7.0.3" dependencies: "@csstools/selector-specificity": "npm:^5.0.0" postcss-selector-parser: "npm:^7.0.0" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/456e9ce1eec8a535683c329956acfe53ce5a208345d7f2fcbe662626be8b3c98681e9041d7f4980316714397b0c1c3defde25653d629c396df17803d599c4edf + checksum: 10c0/c89f68e17bed229e9a3e98da5032e1360c83d45d974bc3fb8d6b5358399bca80cce7929e4a621a516a75536edb78678dc486eb41841eeed28cca79e3be4bdc27 languageName: node linkType: hard @@ -7401,10 +7843,10 @@ __metadata: languageName: node linkType: hard -"cssdb@npm:^8.3.0": - version: 8.3.0 - resolution: "cssdb@npm:8.3.0" - checksum: 10c0/56d13cbddd90e63f45f24f71f35314f9718b72760acdf15367e33014eb45df775ae97ec05c08afaa6b4b147c757e9554c1bf39ddcdaeeb26b6c2adfeee503ae7 +"cssdb@npm:^8.6.0": + version: 8.6.0 + resolution: "cssdb@npm:8.6.0" + checksum: 10c0/4bb7b77ba24902e8d481e9514ec0be56e205186a2b7d9f5027fedfe718952c559c62acfd2859f92869f8090da7c2170f83d68170db5058a6ba8d9d5e8ded3b3e languageName: node linkType: hard @@ -7427,10 +7869,10 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.2": - version: 3.1.3 - resolution: "csstype@npm:3.1.3" - checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 +"csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce languageName: node linkType: hard @@ -7849,6 +8291,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.263": + version: 1.5.267 + resolution: "electron-to-chromium@npm:1.5.267" + checksum: 10c0/0732bdb891b657f2e43266a3db8cf86fff6cecdcc8d693a92beff214e136cb5c2ee7dc5945ed75fa1db16e16bad0c38695527a020d15f39e79084e0b2e447621 + languageName: node + linkType: hard + "electron-to-chromium@npm:^1.5.73": version: 1.5.109 resolution: "electron-to-chromium@npm:1.5.109" @@ -7867,20 +8316,13 @@ __metadata: "@codecov/vite-plugin": "npm:^1.3.0" "@fontsource/inconsolata": "npm:^5.1.0" "@fontsource/inter": "npm:^5.1.0" - "@formatjs/intl-durationformat": "npm:^0.7.0" + "@formatjs/intl-durationformat": "npm:^0.9.0" "@formatjs/intl-segmenter": "npm:^11.7.3" "@livekit/components-core": "npm:^0.12.0" "@livekit/components-react": "npm:^2.0.0" "@livekit/protocol": "npm:^1.42.2" - "@livekit/track-processors": "npm:^0.5.5" + "@livekit/track-processors": "npm:^0.6.0 || ^0.7.1" "@mediapipe/tasks-vision": "npm:^0.10.18" - "@opentelemetry/api": "npm:^1.4.0" - "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/exporter-trace-otlp-http": "npm:^0.203.0" - "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/sdk-trace-base": "npm:^2.0.0" - "@opentelemetry/sdk-trace-web": "npm:^2.0.0" - "@opentelemetry/semantic-conventions": "npm:^1.25.1" "@playwright/test": "npm:^1.57.0" "@radix-ui/react-dialog": "npm:^1.0.4" "@radix-ui/react-slider": "npm:^1.1.2" @@ -7894,7 +8336,6 @@ __metadata: "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.5.1" "@types/content-type": "npm:^1.1.5" - "@types/dom-mediacapture-transform": "npm:^0.1.11" "@types/grecaptcha": "npm:^3.0.9" "@types/jsdom": "npm:^21.1.7" "@types/lodash-es": "npm:^4.17.12" @@ -7929,7 +8370,7 @@ __metadata: eslint-plugin-unicorn: "npm:^56.0.0" fetch-mock: "npm:11.1.5" global-jsdom: "npm:^26.0.0" - i18next: "npm:^24.0.0" + i18next: "npm:^25.0.0" i18next-browser-languagedetector: "npm:^8.0.0" i18next-parser: "npm:^9.1.0" jsdom: "npm:^26.0.0" @@ -7937,7 +8378,7 @@ __metadata: livekit-client: "npm:^2.13.0" lodash-es: "npm:^4.17.21" loglevel: "npm:^1.9.1" - matrix-js-sdk: "matrix-org/matrix-js-sdk#2218ec4e3102e841ba3e794e1c492c0a5aa6c1c3" + matrix-js-sdk: "matrix-org/matrix-js-sdk#develop" matrix-widget-api: "npm:^1.14.0" node-stdlib-browser: "npm:^1.3.1" normalize.css: "npm:^8.0.1" @@ -7950,7 +8391,7 @@ __metadata: qrcode: "npm:^1.5.4" react: "npm:19" react-dom: "npm:19" - react-i18next: "npm:^15.0.0" + react-i18next: "npm:^16.0.0 <16.1.0" react-router-dom: "npm:^7.0.0" react-use-measure: "npm:^2.1.1" rxjs: "npm:^7.8.1" @@ -7958,6 +8399,7 @@ __metadata: typescript: "npm:^5.8.3" typescript-eslint-language-service: "npm:^5.0.5" unique-names-generator: "npm:^4.6.0" + uuid: "npm:^13.0.0" vaul: "npm:^1.0.0" vite: "npm:^7.0.0" vite-plugin-generate-file: "npm:^0.3.0" @@ -8379,6 +8821,95 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.27.0": + version: 0.27.2 + resolution: "esbuild@npm:0.27.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.2" + "@esbuild/android-arm": "npm:0.27.2" + "@esbuild/android-arm64": "npm:0.27.2" + "@esbuild/android-x64": "npm:0.27.2" + "@esbuild/darwin-arm64": "npm:0.27.2" + "@esbuild/darwin-x64": "npm:0.27.2" + "@esbuild/freebsd-arm64": "npm:0.27.2" + "@esbuild/freebsd-x64": "npm:0.27.2" + "@esbuild/linux-arm": "npm:0.27.2" + "@esbuild/linux-arm64": "npm:0.27.2" + "@esbuild/linux-ia32": "npm:0.27.2" + "@esbuild/linux-loong64": "npm:0.27.2" + "@esbuild/linux-mips64el": "npm:0.27.2" + "@esbuild/linux-ppc64": "npm:0.27.2" + "@esbuild/linux-riscv64": "npm:0.27.2" + "@esbuild/linux-s390x": "npm:0.27.2" + "@esbuild/linux-x64": "npm:0.27.2" + "@esbuild/netbsd-arm64": "npm:0.27.2" + "@esbuild/netbsd-x64": "npm:0.27.2" + "@esbuild/openbsd-arm64": "npm:0.27.2" + "@esbuild/openbsd-x64": "npm:0.27.2" + "@esbuild/openharmony-arm64": "npm:0.27.2" + "@esbuild/sunos-x64": "npm:0.27.2" + "@esbuild/win32-arm64": "npm:0.27.2" + "@esbuild/win32-ia32": "npm:0.27.2" + "@esbuild/win32-x64": "npm:0.27.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/cf83f626f55500f521d5fe7f4bc5871bec240d3deb2a01fbd379edc43b3664d1167428738a5aad8794b35d1cca985c44c375b1cd38a2ca613c77ced2c83aafcd + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -9074,21 +9605,21 @@ __metadata: languageName: node linkType: hard -"formatly@npm:^0.2.4": - version: 0.2.4 - resolution: "formatly@npm:0.2.4" +"formatly@npm:^0.3.0": + version: 0.3.0 + resolution: "formatly@npm:0.3.0" dependencies: fd-package-json: "npm:^2.0.0" bin: formatly: bin/index.mjs - checksum: 10c0/43c6272a12199bc6319e7ef7043f209e7005fc35bc1b15e96ef16ad46a12fddc2b7c179fe8ade174c728e8454e3ebdc8428867cee78b082d18a91dae72866336 + checksum: 10c0/ef9dbd3cdaee649e9604ea060d8d62d8131eb81117634336592ee2193fc7c98a3f1f1b5d09a045dbd36287ba88edf868ef179d39fbda2f34fbe2be70c42dd014 languageName: node linkType: hard -"fraction.js@npm:^4.3.7": - version: 4.3.7 - resolution: "fraction.js@npm:4.3.7" - checksum: 10c0/df291391beea9ab4c263487ffd9d17fed162dbb736982dee1379b2a8cc94e4e24e46ed508c6d278aded9080ba51872f1bc5f3a5fd8d7c74e5f105b508ac28711 +"fraction.js@npm:^5.3.4": + version: 5.3.4 + resolution: "fraction.js@npm:5.3.4" + checksum: 10c0/f90079fe9bfc665e0a07079938e8ff71115bce9462f17b32fc283f163b0540ec34dc33df8ed41bb56f028316b04361b9a9995b9ee9258617f8338e0b05c5f95a languageName: node linkType: hard @@ -9789,7 +10320,7 @@ __metadata: languageName: node linkType: hard -"i18next@npm:^23.5.1 || ^24.2.0, i18next@npm:^24.0.0": +"i18next@npm:^23.5.1 || ^24.2.0": version: 24.2.3 resolution: "i18next@npm:24.2.3" dependencies: @@ -9803,6 +10334,20 @@ __metadata: languageName: node linkType: hard +"i18next@npm:^25.0.0": + version: 25.7.3 + resolution: "i18next@npm:25.7.3" + dependencies: + "@babel/runtime": "npm:^7.28.4" + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/0b10452c26d6526bbfa8f0fb76241f5e17f0dc08d9b9cc9810bc3103047a3656ec6482b170a86a408a49178af5683a4b88b43986580c5f95f497d4afc9719088 + languageName: node + linkType: hard + "iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -10378,12 +10923,12 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^2.4.2": - version: 2.4.2 - resolution: "jiti@npm:2.4.2" +"jiti@npm:^2.6.0": + version: 2.6.1 + resolution: "jiti@npm:2.6.1" bin: jiti: lib/jiti-cli.mjs - checksum: 10c0/4ceac133a08c8faff7eac84aabb917e85e8257f5ad659e843004ce76e981c457c390a220881748ac67ba1b940b9b729b30fb85cbaf6e7989f04b6002c94da331 + checksum: 10c0/79b2e96a8e623f66c1b703b98ec1b8be4500e1d217e09b09e343471bbb9c105381b83edbb979d01cef18318cc45ce6e153571b6c83122170eefa531c64b6789b languageName: node linkType: hard @@ -10426,6 +10971,17 @@ __metadata: languageName: node linkType: hard +"js-yaml@npm:^4.1.1": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 + languageName: node + linkType: hard + "jsbn@npm:1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" @@ -10609,29 +11165,28 @@ __metadata: linkType: hard "knip@npm:^5.27.2": - version: 5.62.0 - resolution: "knip@npm:5.62.0" + version: 5.79.0 + resolution: "knip@npm:5.79.0" dependencies: "@nodelib/fs.walk": "npm:^1.2.3" fast-glob: "npm:^3.3.3" - formatly: "npm:^0.2.4" - jiti: "npm:^2.4.2" - js-yaml: "npm:^4.1.0" + formatly: "npm:^0.3.0" + jiti: "npm:^2.6.0" + js-yaml: "npm:^4.1.1" minimist: "npm:^1.2.8" - oxc-resolver: "npm:^11.1.0" + oxc-resolver: "npm:^11.15.0" picocolors: "npm:^1.1.1" picomatch: "npm:^4.0.1" - smol-toml: "npm:^1.3.4" - strip-json-comments: "npm:5.0.2" - zod: "npm:^3.22.4" - zod-validation-error: "npm:^3.0.3" + smol-toml: "npm:^1.5.2" + strip-json-comments: "npm:5.0.3" + zod: "npm:^4.1.11" peerDependencies: "@types/node": ">=18" - typescript: ">=5.0.4" + typescript: ">=5.0.4 <7" bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: 10c0/ffc6c123d132bb423936859c3ae5cb85154cfc862985f74637bc54a4920ef34c1f19d41020f7af25100ea8e7ae61f1f5279af8066043434444bcba82b8dc1611 + checksum: 10c0/dc3599247763912c0602621b83d125cba4e111d85ec5f01f9b65808a0091a60d7be85ed6cecc93d0afb39b895127231f7a68dd4c4bb7e210dd727b6ef9c1571d languageName: node linkType: hard @@ -10683,8 +11238,8 @@ __metadata: linkType: hard "livekit-client@npm:^2.13.0": - version: 2.16.1 - resolution: "livekit-client@npm:2.16.1" + version: 2.16.0 + resolution: "livekit-client@npm:2.16.0" dependencies: "@livekit/mutex": "npm:1.1.1" "@livekit/protocol": "npm:1.42.2" @@ -10698,7 +11253,7 @@ __metadata: webrtc-adapter: "npm:^9.0.1" peerDependencies: "@types/dom-mediacapture-record": ^1 - checksum: 10c0/a16f7e603730410b640991cdb1a9d5ad3b0b06b23b8fe2e76674e6a9ba54ed4c8e7e4f6ad15fe67c8de6557da161e1265b40802c4e3701db3fe0d7ea9250b1e2 + checksum: 10c0/5d03adc5d09efde343ab894db397529dff26117598e773b23a5df90a4fb166bde12c6bb1f2cfd1d28dbaf93fe9f275026d7abb75f2ffd2ba816393a2d58e6c7e languageName: node linkType: hard @@ -10721,9 +11276,9 @@ __metadata: linkType: hard "lodash-es@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash-es@npm:4.17.21" - checksum: 10c0/fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 + version: 4.17.22 + resolution: "lodash-es@npm:4.17.22" + checksum: 10c0/5f28a262183cca43e08c580622557f393cb889386df2d8adf7c852bfdff7a84c5e629df5aa6c5c6274e83b38172f239d3e4e72e1ad27352d9ae9766627338089 languageName: node linkType: hard @@ -10741,13 +11296,6 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c - languageName: node - linkType: hard - "loglevel@npm:1.9.1": version: 1.9.1 resolution: "loglevel@npm:1.9.1" @@ -10762,13 +11310,6 @@ __metadata: languageName: node linkType: hard -"long@npm:^5.0.0": - version: 5.3.1 - resolution: "long@npm:5.3.1" - checksum: 10c0/8726994c6359bb7162fb94563e14c3f9c0f0eeafd90ec654738f4f144a5705756d36a873c442f172ee2a4b51e08d14ab99765b49aa1fb994c5ba7fe12057bca2 - languageName: node - linkType: hard - "loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -10925,12 +11466,12 @@ __metadata: languageName: node linkType: hard -"matrix-js-sdk@matrix-org/matrix-js-sdk#2218ec4e3102e841ba3e794e1c492c0a5aa6c1c3": - version: 39.3.0 - resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=2218ec4e3102e841ba3e794e1c492c0a5aa6c1c3" +"matrix-js-sdk@matrix-org/matrix-js-sdk#develop": + version: 39.4.0 + resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=4d0d32307eb4f1ce1fb65080fcca704f5bdedc31" dependencies: "@babel/runtime": "npm:^7.12.5" - "@matrix-org/matrix-sdk-crypto-wasm": "npm:^16.0.0" + "@matrix-org/matrix-sdk-crypto-wasm": "npm:^17.0.0" another-json: "npm:^0.2.0" bs58: "npm:^6.0.0" content-type: "npm:^1.0.4" @@ -10943,7 +11484,7 @@ __metadata: sdp-transform: "npm:^3.0.0" unhomoglyph: "npm:^1.0.6" uuid: "npm:13" - checksum: 10c0/feca51c7ada5a56aa6cfb74f29bd1640a20804e9de689d23f10c5227e07ba4f66ebbb9606e1384390dca277a6942886706198394717694a9cfb1f20cd36ca377 + checksum: 10c0/59c9d81ccf823584dc783502cb5c928562e3490c63f5ce98ee3232a603545d6278e90dc951c1fd0bae2792ba732ec5171e03596fd396bb2150d596cebb7fbac9 languageName: node linkType: hard @@ -11284,6 +11825,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10c0/f1e6583b7833ea81880627748d28a3a7ff5703d5409328c216ae57befbced10ce2c991bea86434e8ec39003bd017f70481e2e5f8c1f7e0a7663241f81d6e00e2 + languageName: node + linkType: hard + "node-stdlib-browser@npm:^1.3.1": version: 1.3.1 resolution: "node-stdlib-browser@npm:1.3.1" @@ -11349,13 +11897,6 @@ __metadata: languageName: node linkType: hard -"normalize-range@npm:^0.1.2": - version: 0.1.2 - resolution: "normalize-range@npm:0.1.2" - checksum: 10c0/bf39b73a63e0a42ad1a48c2bd1bda5a07ede64a7e2567307a407674e595bcff0fa0d57e8e5f1e7fa5e91000797c7615e13613227aaaa4d6d6e87f5bd5cc95de6 - languageName: node - linkType: hard - "normalize.css@npm:^8.0.1": version: 8.0.1 resolution: "normalize.css@npm:8.0.1" @@ -11555,24 +12096,35 @@ __metadata: languageName: node linkType: hard -"oxc-resolver@npm:^11.1.0": - version: 11.3.0 - resolution: "oxc-resolver@npm:11.3.0" +"oxc-resolver@npm:^11.15.0": + version: 11.16.2 + resolution: "oxc-resolver@npm:11.16.2" dependencies: - "@oxc-resolver/binding-darwin-arm64": "npm:11.3.0" - "@oxc-resolver/binding-darwin-x64": "npm:11.3.0" - "@oxc-resolver/binding-freebsd-x64": "npm:11.3.0" - "@oxc-resolver/binding-linux-arm-gnueabihf": "npm:11.3.0" - "@oxc-resolver/binding-linux-arm64-gnu": "npm:11.3.0" - "@oxc-resolver/binding-linux-arm64-musl": "npm:11.3.0" - "@oxc-resolver/binding-linux-riscv64-gnu": "npm:11.3.0" - "@oxc-resolver/binding-linux-s390x-gnu": "npm:11.3.0" - "@oxc-resolver/binding-linux-x64-gnu": "npm:11.3.0" - "@oxc-resolver/binding-linux-x64-musl": "npm:11.3.0" - "@oxc-resolver/binding-wasm32-wasi": "npm:11.3.0" - "@oxc-resolver/binding-win32-arm64-msvc": "npm:11.3.0" - "@oxc-resolver/binding-win32-x64-msvc": "npm:11.3.0" + "@oxc-resolver/binding-android-arm-eabi": "npm:11.16.2" + "@oxc-resolver/binding-android-arm64": "npm:11.16.2" + "@oxc-resolver/binding-darwin-arm64": "npm:11.16.2" + "@oxc-resolver/binding-darwin-x64": "npm:11.16.2" + "@oxc-resolver/binding-freebsd-x64": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm-gnueabihf": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm-musleabihf": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm64-musl": "npm:11.16.2" + "@oxc-resolver/binding-linux-ppc64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-riscv64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-riscv64-musl": "npm:11.16.2" + "@oxc-resolver/binding-linux-s390x-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-x64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-x64-musl": "npm:11.16.2" + "@oxc-resolver/binding-openharmony-arm64": "npm:11.16.2" + "@oxc-resolver/binding-wasm32-wasi": "npm:11.16.2" + "@oxc-resolver/binding-win32-arm64-msvc": "npm:11.16.2" + "@oxc-resolver/binding-win32-ia32-msvc": "npm:11.16.2" + "@oxc-resolver/binding-win32-x64-msvc": "npm:11.16.2" dependenciesMeta: + "@oxc-resolver/binding-android-arm-eabi": + optional: true + "@oxc-resolver/binding-android-arm64": + optional: true "@oxc-resolver/binding-darwin-arm64": optional: true "@oxc-resolver/binding-darwin-x64": @@ -11581,25 +12133,35 @@ __metadata: optional: true "@oxc-resolver/binding-linux-arm-gnueabihf": optional: true + "@oxc-resolver/binding-linux-arm-musleabihf": + optional: true "@oxc-resolver/binding-linux-arm64-gnu": optional: true "@oxc-resolver/binding-linux-arm64-musl": optional: true + "@oxc-resolver/binding-linux-ppc64-gnu": + optional: true "@oxc-resolver/binding-linux-riscv64-gnu": optional: true + "@oxc-resolver/binding-linux-riscv64-musl": + optional: true "@oxc-resolver/binding-linux-s390x-gnu": optional: true "@oxc-resolver/binding-linux-x64-gnu": optional: true "@oxc-resolver/binding-linux-x64-musl": optional: true + "@oxc-resolver/binding-openharmony-arm64": + optional: true "@oxc-resolver/binding-wasm32-wasi": optional: true "@oxc-resolver/binding-win32-arm64-msvc": optional: true + "@oxc-resolver/binding-win32-ia32-msvc": + optional: true "@oxc-resolver/binding-win32-x64-msvc": optional: true - checksum: 10c0/9437976bd39125538e031b89e75e29e7340b844da16afa1088d2c2a21770051a81f583dd77823894d7065ee2dc6431d8c5f158ac227035d33b430b78efa358dc + checksum: 10c0/b20a0fea18fdf31dbaee51354ce7b987ba8f3e780c6c1de9034628033a69d0b3085f9596d9925797d9340bdf4b98cd72a258b0728d0d5e5de2b1748154921b42 languageName: node linkType: hard @@ -11875,7 +12437,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": +"picocolors@npm:1.1.1, picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 @@ -11979,18 +12541,18 @@ __metadata: languageName: node linkType: hard -"postcss-color-functional-notation@npm:^7.0.10": - version: 7.0.10 - resolution: "postcss-color-functional-notation@npm:7.0.10" +"postcss-color-functional-notation@npm:^7.0.12": + version: 7.0.12 + resolution: "postcss-color-functional-notation@npm:7.0.12" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/62ee77ef220488cfb4a1c5af4f5203a0c2951c8a0613088ffc946130d48b63ca28ab67b18ed380a288a7ce51c2360a75d8d08d2db389e48f4ebb78a3e52d15b6 + checksum: 10c0/dc80ba1a956ae9b396596bda72d9bdb92de96874378a38ba4e2177ffa35339dc76d894920bb013b6f10c9b75cfb41778e09956a438c2e9ea41b684f766c55f4a languageName: node linkType: hard @@ -12072,16 +12634,16 @@ __metadata: languageName: node linkType: hard -"postcss-double-position-gradients@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-double-position-gradients@npm:6.0.2" +"postcss-double-position-gradients@npm:^6.0.4": + version: 6.0.4 + resolution: "postcss-double-position-gradients@npm:6.0.4" dependencies: - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/7b4759813f99039c6a7c8e70b46ff4c34c27e723a9ff7f0e1044e293d568357e1d39233f94b1bf3b2768b1207348138faea0781086a66b7b8e39e780657da523 + checksum: 10c0/6dbbe7a3855e84a9319df434e210225f6dfa7262e5959611355f1769c2c9d30d37a19737712f20eac6354876fff4ba556d8d0b12a90c78d8ab97c9a8da534a7c languageName: node linkType: hard @@ -12137,18 +12699,18 @@ __metadata: languageName: node linkType: hard -"postcss-lab-function@npm:^7.0.10": - version: 7.0.10 - resolution: "postcss-lab-function@npm:7.0.10" +"postcss-lab-function@npm:^7.0.12": + version: 7.0.12 + resolution: "postcss-lab-function@npm:7.0.12" dependencies: - "@csstools/css-color-parser": "npm:^3.0.10" + "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/3e235b52f6c119937a0b41aa351f5f9ef6e17bf1b868e7068c9a04f3d31c247d0296c862388febb7fec5102d81413ccade8a4788904289afd34aa072de71390b + checksum: 10c0/de39b59da3b97c18d055d81fba68993e93253184ed76f103c888273584f868c551d047814dd54445980a1bdc5987e8f8af141383d84ecc641e5a6ee7bd901095 languageName: node linkType: hard @@ -12217,23 +12779,26 @@ __metadata: linkType: hard "postcss-preset-env@npm:^10.0.0": - version: 10.2.4 - resolution: "postcss-preset-env@npm:10.2.4" + version: 10.6.0 + resolution: "postcss-preset-env@npm:10.6.0" dependencies: + "@csstools/postcss-alpha-function": "npm:^1.0.1" "@csstools/postcss-cascade-layers": "npm:^5.0.2" - "@csstools/postcss-color-function": "npm:^4.0.10" - "@csstools/postcss-color-mix-function": "npm:^3.0.10" - "@csstools/postcss-color-mix-variadic-function-arguments": "npm:^1.0.0" - "@csstools/postcss-content-alt-text": "npm:^2.0.6" + "@csstools/postcss-color-function": "npm:^4.0.12" + "@csstools/postcss-color-function-display-p3-linear": "npm:^1.0.1" + "@csstools/postcss-color-mix-function": "npm:^3.0.12" + "@csstools/postcss-color-mix-variadic-function-arguments": "npm:^1.0.2" + "@csstools/postcss-content-alt-text": "npm:^2.0.8" + "@csstools/postcss-contrast-color-function": "npm:^2.0.12" "@csstools/postcss-exponential-functions": "npm:^2.0.9" "@csstools/postcss-font-format-keywords": "npm:^4.0.0" - "@csstools/postcss-gamut-mapping": "npm:^2.0.10" - "@csstools/postcss-gradients-interpolation-method": "npm:^5.0.10" - "@csstools/postcss-hwb-function": "npm:^4.0.10" - "@csstools/postcss-ic-unit": "npm:^4.0.2" + "@csstools/postcss-gamut-mapping": "npm:^2.0.11" + "@csstools/postcss-gradients-interpolation-method": "npm:^5.0.12" + "@csstools/postcss-hwb-function": "npm:^4.0.12" + "@csstools/postcss-ic-unit": "npm:^4.0.4" "@csstools/postcss-initial": "npm:^2.0.1" "@csstools/postcss-is-pseudo-class": "npm:^5.0.3" - "@csstools/postcss-light-dark-function": "npm:^2.0.9" + "@csstools/postcss-light-dark-function": "npm:^2.0.11" "@csstools/postcss-logical-float-and-clear": "npm:^3.0.0" "@csstools/postcss-logical-overflow": "npm:^2.0.0" "@csstools/postcss-logical-overscroll-behavior": "npm:^2.0.0" @@ -12243,38 +12808,42 @@ __metadata: "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^3.0.5" "@csstools/postcss-nested-calc": "npm:^4.0.0" "@csstools/postcss-normalize-display-values": "npm:^4.0.0" - "@csstools/postcss-oklab-function": "npm:^4.0.10" - "@csstools/postcss-progressive-custom-properties": "npm:^4.1.0" + "@csstools/postcss-oklab-function": "npm:^4.0.12" + "@csstools/postcss-position-area-property": "npm:^1.0.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/postcss-property-rule-prelude-list": "npm:^1.0.0" "@csstools/postcss-random-function": "npm:^2.0.1" - "@csstools/postcss-relative-color-syntax": "npm:^3.0.10" + "@csstools/postcss-relative-color-syntax": "npm:^3.0.12" "@csstools/postcss-scope-pseudo-class": "npm:^4.0.1" "@csstools/postcss-sign-functions": "npm:^1.1.4" "@csstools/postcss-stepped-value-functions": "npm:^4.0.9" - "@csstools/postcss-text-decoration-shorthand": "npm:^4.0.2" + "@csstools/postcss-syntax-descriptor-syntax-production": "npm:^1.0.1" + "@csstools/postcss-system-ui-font-family": "npm:^1.0.0" + "@csstools/postcss-text-decoration-shorthand": "npm:^4.0.3" "@csstools/postcss-trigonometric-functions": "npm:^4.0.9" "@csstools/postcss-unset-value": "npm:^4.0.0" - autoprefixer: "npm:^10.4.21" - browserslist: "npm:^4.25.0" + autoprefixer: "npm:^10.4.23" + browserslist: "npm:^4.28.1" css-blank-pseudo: "npm:^7.0.1" - css-has-pseudo: "npm:^7.0.2" + css-has-pseudo: "npm:^7.0.3" css-prefers-color-scheme: "npm:^10.0.0" - cssdb: "npm:^8.3.0" + cssdb: "npm:^8.6.0" postcss-attribute-case-insensitive: "npm:^7.0.1" postcss-clamp: "npm:^4.1.0" - postcss-color-functional-notation: "npm:^7.0.10" + postcss-color-functional-notation: "npm:^7.0.12" postcss-color-hex-alpha: "npm:^10.0.0" postcss-color-rebeccapurple: "npm:^10.0.0" postcss-custom-media: "npm:^11.0.6" postcss-custom-properties: "npm:^14.0.6" postcss-custom-selectors: "npm:^8.0.5" postcss-dir-pseudo-class: "npm:^9.0.1" - postcss-double-position-gradients: "npm:^6.0.2" + postcss-double-position-gradients: "npm:^6.0.4" postcss-focus-visible: "npm:^10.0.1" postcss-focus-within: "npm:^9.0.1" postcss-font-variant: "npm:^5.0.0" postcss-gap-properties: "npm:^6.0.0" postcss-image-set-function: "npm:^7.0.0" - postcss-lab-function: "npm:^7.0.10" + postcss-lab-function: "npm:^7.0.12" postcss-logical: "npm:^8.1.0" postcss-nesting: "npm:^13.0.2" postcss-opacity-percentage: "npm:^3.0.0" @@ -12286,7 +12855,7 @@ __metadata: postcss-selector-not: "npm:^8.0.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/d7f8494d355567dc4ea66fe765c86ba9b1e9ce5061ada5c80c51fdf6c98b004b0b7ef17b5f64d197e1bec2e22ef4b6c613b998e1c1bcad0b53f0a3e303ded2fe + checksum: 10c0/61162c9d675004db842d58829605c3c9ee81ed1a15684793a419b94c2c28e3be2ff9a7373f0996a1a255caf208d8f3d5dd907e61af1bbb0c7634e3215e87fc56 languageName: node linkType: hard @@ -12375,11 +12944,11 @@ __metadata: linkType: hard "prettier@npm:^3.0.0": - version: 3.6.2 - resolution: "prettier@npm:3.6.2" + version: 3.7.4 + resolution: "prettier@npm:3.7.4" bin: prettier: bin/prettier.cjs - checksum: 10c0/488cb2f2b99ec13da1e50074912870217c11edaddedeadc649b1244c749d15ba94e846423d062e2c4c9ae683e2d65f754de28889ba06e697ac4f988d44f45812 + checksum: 10c0/9675d2cd08eacb1faf1d1a2dbfe24bfab6a912b059fc9defdb380a408893d88213e794a40a2700bd29b140eb3172e0b07c852853f6e22f16f3374659a1a13389 languageName: node linkType: hard @@ -12460,26 +13029,6 @@ __metadata: languageName: node linkType: hard -"protobufjs@npm:^7.3.0": - version: 7.4.0 - resolution: "protobufjs@npm:7.4.0" - dependencies: - "@protobufjs/aspromise": "npm:^1.1.2" - "@protobufjs/base64": "npm:^1.1.2" - "@protobufjs/codegen": "npm:^2.0.4" - "@protobufjs/eventemitter": "npm:^1.1.0" - "@protobufjs/fetch": "npm:^1.1.0" - "@protobufjs/float": "npm:^1.0.2" - "@protobufjs/inquire": "npm:^1.1.0" - "@protobufjs/path": "npm:^1.1.2" - "@protobufjs/pool": "npm:^1.1.0" - "@protobufjs/utf8": "npm:^1.1.0" - "@types/node": "npm:>=13.7.0" - long: "npm:^5.0.0" - checksum: 10c0/a5460a63fe596523b9a067cbce39a6b310d1a71750fda261f076535662aada97c24450e18c5bc98a27784f70500615904ff1227e1742183509f0db4fdede669b - languageName: node - linkType: hard - "proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" @@ -12582,24 +13131,24 @@ __metadata: linkType: hard "react-dom@npm:19": - version: 19.1.0 - resolution: "react-dom@npm:19.1.0" + version: 19.2.3 + resolution: "react-dom@npm:19.2.3" dependencies: - scheduler: "npm:^0.26.0" + scheduler: "npm:^0.27.0" peerDependencies: - react: ^19.1.0 - checksum: 10c0/3e26e89bb6c67c9a6aa86cb888c7a7f8258f2e347a6d2a15299c17eb16e04c19194e3452bc3255bd34000a61e45e2cb51e46292392340432f133e5a5d2dfb5fc + react: ^19.2.3 + checksum: 10c0/dc43f7ede06f46f3acc16ee83107c925530de9b91d1d0b3824583814746ff4c498ea64fd65cd83aba363205268adff52e2827c582634ae7b15069deaeabc4892 languageName: node linkType: hard -"react-i18next@npm:^15.0.0": - version: 15.6.1 - resolution: "react-i18next@npm:15.6.1" +"react-i18next@npm:^16.0.0 <16.1.0": + version: 16.0.1 + resolution: "react-i18next@npm:16.0.1" dependencies: "@babel/runtime": "npm:^7.27.6" html-parse-stringify: "npm:^3.0.1" peerDependencies: - i18next: ">= 23.2.3" + i18next: ">= 25.5.2" react: ">= 16.8.0" typescript: ^5 peerDependenciesMeta: @@ -12609,7 +13158,7 @@ __metadata: optional: true typescript: optional: true - checksum: 10c0/10cd131005a70a493e307f4f710ea9921d5a955a5281064cad79162e5051e2bcfc119f50c7bee0fad06e33a1795308a006089d5b67969d52b66afbe149514305 + checksum: 10c0/8fcd8dea9bd083aac37acf569478872980216d2f2d85eff18444316a407832f1d3f589d1d0e587c6fe709d43423f5ec82c2a96f52cc2999ddd2180fa3de20ace languageName: node linkType: hard @@ -12689,20 +13238,20 @@ __metadata: linkType: hard "react-router-dom@npm:^7.0.0": - version: 7.7.1 - resolution: "react-router-dom@npm:7.7.1" + version: 7.11.0 + resolution: "react-router-dom@npm:7.11.0" dependencies: - react-router: "npm:7.7.1" + react-router: "npm:7.11.0" peerDependencies: react: ">=18" react-dom: ">=18" - checksum: 10c0/292455db6991d8559a9e94857440393d9fd011471ff231f8c3a40be6749f74347f20c183a2e9b52830ec54d2bf3b2e1310c006e709e66b27206b0c36ab54def6 + checksum: 10c0/0e8061fe0ef7915cc411dd92f5f41109f6343b6abef36571b08ff231365bf61f52364bea128d1c964e9b8eb19426c9bd21923df0b3e1bb993d21bd2b7440fb49 languageName: node linkType: hard -"react-router@npm:7.7.1": - version: 7.7.1 - resolution: "react-router@npm:7.7.1" +"react-router@npm:7.11.0": + version: 7.11.0 + resolution: "react-router@npm:7.11.0" dependencies: cookie: "npm:^1.0.1" set-cookie-parser: "npm:^2.6.0" @@ -12712,7 +13261,7 @@ __metadata: peerDependenciesMeta: react-dom: optional: true - checksum: 10c0/e55fe74a2947939526c79e496ab1fc501fd8e89a191a20157d94cfe712d4d9d84f68627811cf1d477a36b98250fcad958bf1237fc41ff0a8b2de00ddc8c53e3b + checksum: 10c0/eb3693d63d1c52221a3449de5db170e2fa9e00536b011998b17f8a277f8b5e89b752d104dbbeb4ee3d474f8e4570167db00293b4510f63277e5e6658c5dab22b languageName: node linkType: hard @@ -12746,9 +13295,9 @@ __metadata: linkType: hard "react@npm:19": - version: 19.1.0 - resolution: "react@npm:19.1.0" - checksum: 10c0/530fb9a62237d54137a13d2cfb67a7db6a2156faed43eecc423f4713d9b20c6f2728b026b45e28fcd72e8eadb9e9ed4b089e99f5e295d2f0ad3134251bdd3698 + version: 19.2.3 + resolution: "react@npm:19.2.3" + checksum: 10c0/094220b3ba3a76c1b668f972ace1dd15509b157aead1b40391d1c8e657e720c201d9719537375eff08f5e0514748c0319063392a6f000e31303aafc4471f1436 languageName: node linkType: hard @@ -13362,8 +13911,8 @@ __metadata: linkType: hard "sass@npm:^1.42.1": - version: 1.89.2 - resolution: "sass@npm:1.89.2" + version: 1.97.1 + resolution: "sass@npm:1.97.1" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" @@ -13374,7 +13923,7 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10c0/752ccc7581b0c6395f63918116c20924e99943a86d79e94f5c4a0d41b1e981fe1f0ecd1ee82fff21496f81dbc91f68fb35a498166562ec8ec53e7aad7c3dbd9d + checksum: 10c0/c389d5d6405869b49fa2291e8328500fe7936f3b72136bc2c338bee6e7fec936bb9a48d77a1310dea66aa4669ba74ae6b82a112eb32521b9b36d740138a39ea0 languageName: node linkType: hard @@ -13387,10 +13936,10 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.26.0": - version: 0.26.0 - resolution: "scheduler@npm:0.26.0" - checksum: 10c0/5b8d5bfddaae3513410eda54f2268e98a376a429931921a81b5c3a2873aab7ca4d775a8caac5498f8cbc7d0daeab947cf923dbd8e215d61671f9f4e392d34356 +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 languageName: node linkType: hard @@ -13634,10 +14183,10 @@ __metadata: languageName: node linkType: hard -"smol-toml@npm:^1.3.4": - version: 1.4.0 - resolution: "smol-toml@npm:1.4.0" - checksum: 10c0/5f46599c8404ab9e4f9328b3a4f84d0eb01764279826424d225c5c2f3e36d0ee5533eda81aa6507fb24597fd0909611a57273f513a03545e1ad54ee4642ad94f +"smol-toml@npm:^1.5.2": + version: 1.6.0 + resolution: "smol-toml@npm:1.6.0" + checksum: 10c0/baf33bb6cd914d481329e31998a12829cd126541458ba400791212c80f1245d5b27dac04a56a52c02b287d2a494f1628c05fc19643286b258b2e0bb9fe67747c languageName: node linkType: hard @@ -13988,10 +14537,10 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:5.0.2": - version: 5.0.2 - resolution: "strip-json-comments@npm:5.0.2" - checksum: 10c0/e9841b8face78a01b0eb66f81e0a3419186a96f1d26817a5e1f5260b0631c10e0a7f711dddc5988edf599e5c079e4dd6e91defd21523e556636ba5679786f5ac +"strip-json-comments@npm:5.0.3": + version: 5.0.3 + resolution: "strip-json-comments@npm:5.0.3" + checksum: 10c0/daaf20b29f69fb51112698f4a9a662490dbb78d5baf6127c75a0a83c2ac6c078a8c0f74b389ad5e0519d6fc359c4a57cb9971b1ae201aef62ce45a13247791e0 languageName: node linkType: hard @@ -14309,12 +14858,12 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.1.0": - version: 2.1.0 - resolution: "ts-api-utils@npm:2.1.0" +"ts-api-utils@npm:^2.2.0": + version: 2.4.0 + resolution: "ts-api-utils@npm:2.4.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10c0/9806a38adea2db0f6aa217ccc6bc9c391ddba338a9fe3080676d0d50ed806d305bb90e8cef0276e793d28c8a929f400abb184ddd7ff83a416959c0f4d2ce754f + checksum: 10c0/ed185861aef4e7124366a3f6561113557a57504267d4d452a51e0ba516a9b6e713b56b4aeaab9fa13de9db9ab755c65c8c13a777dba9133c214632cb7b65c083 languageName: node linkType: hard @@ -14499,12 +15048,12 @@ __metadata: linkType: hard "typescript@npm:^5.0.4": - version: 5.8.2 - resolution: "typescript@npm:5.8.2" + version: 5.9.3 + resolution: "typescript@npm:5.9.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6 + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 languageName: node linkType: hard @@ -14519,12 +15068,12 @@ __metadata: linkType: hard "typescript@patch:typescript@npm%3A^5.0.4#optional!builtin": - version: 5.8.2 - resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2 + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 languageName: node linkType: hard @@ -14567,13 +15116,6 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~7.8.0": - version: 7.8.0 - resolution: "undici-types@npm:7.8.0" - checksum: 10c0/9d9d246d1dc32f318d46116efe3cfca5a72d4f16828febc1918d94e58f6ffcf39c158aa28bf5b4fc52f410446bc7858f35151367bd7a49f21746cab6497b709b - languageName: node - linkType: hard - "undici@npm:^5.25.4": version: 5.29.0 resolution: "undici@npm:5.29.0" @@ -14710,6 +15252,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.2.0": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -14791,7 +15347,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:13": +"uuid@npm:13, uuid@npm:^13.0.0": version: 13.0.0 resolution: "uuid@npm:13.0.0" bin: @@ -14950,23 +15506,23 @@ __metadata: linkType: hard "vite-plugin-svgr@npm:^4.0.0": - version: 4.3.0 - resolution: "vite-plugin-svgr@npm:4.3.0" + version: 4.5.0 + resolution: "vite-plugin-svgr@npm:4.5.0" dependencies: - "@rollup/pluginutils": "npm:^5.1.3" + "@rollup/pluginutils": "npm:^5.2.0" "@svgr/core": "npm:^8.1.0" "@svgr/plugin-jsx": "npm:^8.1.0" peerDependencies: vite: ">=2.6.0" - checksum: 10c0/a73f10d319f72cd8c16bf9701cf18170f2300f98c72c6bf939565de0b1e93916bd70c6f5a446dc034b4405c72d382655c7c16be4bd1cbf35bbcde5febf7aeffc + checksum: 10c0/3e1959fec626bb4f5a8ec13ff15bc40ffbc1c0ff38149bebe3f37dc2d67ed1f276f129ff7983e06946cf712e19996affd9d6868aa7d20d8921d1fe4449109b55 languageName: node linkType: hard "vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0, vite@npm:^7.0.0": - version: 7.1.12 - resolution: "vite@npm:7.1.12" + version: 7.3.0 + resolution: "vite@npm:7.3.0" dependencies: - esbuild: "npm:^0.25.0" + esbuild: "npm:^0.27.0" fdir: "npm:^6.5.0" fsevents: "npm:~2.3.3" picomatch: "npm:^4.0.3" @@ -15013,7 +15569,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/cef4d4b4a84e663e09b858964af36e916892ac8540068df42a05ced637ceeae5e9ef71c72d54f3cfc1f3c254af16634230e221b6e2327c2a66d794bb49203262 + checksum: 10c0/0457c196cdd5761ec351c0f353945430fbad330e615b9eeab729c8ae163334f18acdc1d9cd7d9d673dbf111f07f6e4f0b25d4ac32360e65b4a6df9991046f3ff languageName: node linkType: hard @@ -15518,18 +16074,16 @@ __metadata: languageName: node linkType: hard -"zod-validation-error@npm:^3.0.3": - version: 3.4.0 - resolution: "zod-validation-error@npm:3.4.0" - peerDependencies: - zod: ^3.18.0 - checksum: 10c0/aaadb0e65c834aacb12fa088663d52d9f4224b5fe6958f09b039f4ab74145fda381c8a7d470bfddf7ddd9bbb5fdfbb52739cd66958ce6d388c256a44094d1fba - languageName: node - linkType: hard - "zod@npm:^3.22.4": version: 3.24.2 resolution: "zod@npm:3.24.2" checksum: 10c0/c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565 languageName: node linkType: hard + +"zod@npm:^4.1.11": + version: 4.3.4 + resolution: "zod@npm:4.3.4" + checksum: 10c0/a096102c8b31ecdb913bacb08d5a0fe8447bbe4f54cff421a0c5830a5552da76aae9fd8a01f2e9fdeaae35da1a73762551bc9d14cfedb13a44056de1ed2eb76f + languageName: node + linkType: hard