diff --git a/js/mobile/app.config.ts b/js/mobile/app.config.ts
index 0069c71..daa8413 100644
--- a/js/mobile/app.config.ts
+++ b/js/mobile/app.config.ts
@@ -57,6 +57,11 @@ const config: ExpoConfig = {
color: "#000000",
},
],
+ // LiveKit needs WebRTC native modules. The two plugins below patch the
+ // iOS/Android prebuild so camera/mic permissions and the webrtc pod are
+ // wired up correctly for huddles.
+ "@config-plugins/react-native-webrtc",
+ "@livekit/react-native-expo-plugin",
],
experiments: {
typedRoutes: false,
diff --git a/js/mobile/index.ts b/js/mobile/index.ts
index f45fcf4..e0a2575 100644
--- a/js/mobile/index.ts
+++ b/js/mobile/index.ts
@@ -1,5 +1,11 @@
import "./global.css";
+import { registerGlobals } from "@livekit/react-native";
import { registerRootComponent } from "expo";
import App from "./src/App";
+// LiveKit pipes WebRTC globals (RTCPeerConnection, mediaDevices, ...) onto the
+// JS global. Must run before any LiveKit client is constructed, so do it at
+// app entry rather than inside the huddle screen.
+registerGlobals();
+
registerRootComponent(App);
diff --git a/js/mobile/package.json b/js/mobile/package.json
index 4fe8aea..84547c4 100644
--- a/js/mobile/package.json
+++ b/js/mobile/package.json
@@ -4,7 +4,7 @@
"private": true,
"main": "index.ts",
"scripts": {
- "start": "expo start",
+ "start": "expo start -c",
"ios": "expo run:ios --device",
"publish:ios": "eas build --platform ios --auto-submit && echo 'Go to App Store Connect and submit the testflight build for app review. Visit for more information: https://docs.expo.dev/submit/introduction/'",
"android": "expo run:android",
@@ -30,6 +30,11 @@
"expo-status-bar": "~3.0.9",
"expo-video": "~3.0.10",
"firebase": "^12.10.0",
+ "@config-plugins/react-native-webrtc": "^12.0.0",
+ "@livekit/react-native": "^2.7.5",
+ "@livekit/react-native-expo-plugin": "^1.0.2",
+ "@livekit/react-native-webrtc": "^144.1.0",
+ "livekit-client": "^2.15.2",
"lucide-react-native": "^0.575.0",
"nativewind": "^4.1.23",
"react": "19.1.0",
diff --git a/js/mobile/src/features/huddle/HuddleScreen.tsx b/js/mobile/src/features/huddle/HuddleScreen.tsx
new file mode 100644
index 0000000..c307f2f
--- /dev/null
+++ b/js/mobile/src/features/huddle/HuddleScreen.tsx
@@ -0,0 +1,302 @@
+import { useCallback, useEffect } from "react";
+import {
+ Alert,
+ Dimensions,
+ Pressable,
+ Text,
+ View,
+} from "react-native";
+import type { Human } from "@/api/types";
+import { SafeAreaView } from "react-native-safe-area-context";
+import { StatusBar } from "expo-status-bar";
+import {
+ AudioSession,
+ LiveKitRoom,
+ VideoTrack,
+ isTrackReference,
+ useLocalParticipant,
+ useRoomContext,
+ useTracks,
+} from "@livekit/react-native";
+import type { TrackReferenceOrPlaceholder } from "@livekit/components-core";
+import { Track } from "livekit-client";
+import { Mic, MicOff, PhoneOff, Video, VideoOff } from "lucide-react-native";
+import { useNetwork } from "@/hooks/use-networks";
+import { resolveHumanDisplay } from "@/lib/humans";
+import { cn } from "@/lib/utils";
+import type { RootStackScreenProps } from "@/navigation/types";
+
+/**
+ * Mobile huddle screen — LiveKit room with a tile grid, basic mic/camera
+ * controls, and a leave button. Mirrors the desktop HuddleApp but tuned for
+ * touch: bigger controls, grid layout instead of focus/carousel, no chat or
+ * screenshare (camera + audio only).
+ */
+export function HuddleScreen({
+ route,
+ navigation,
+}: RootStackScreenProps<"Huddle">) {
+ const { token, serverUrl, streamName, networkId } = route.params;
+
+ // iOS in particular requires us to bracket the room session with
+ // start/stop calls so the AVAudioSession is configured for VoIP routing
+ // (earpiece → speaker, ducking, etc.). Without this, remote audio is
+ // routed to the receiver speaker and the user has to hold the phone to
+ // their ear to hear anyone.
+ useEffect(() => {
+ void AudioSession.startAudioSession().catch(() => {
+ // Non-fatal: room will still connect, audio routing may just be
+ // less ideal. Failing the whole screen here would feel worse.
+ });
+ return () => {
+ void AudioSession.stopAudioSession();
+ };
+ }, []);
+
+ const leave = useCallback(() => {
+ navigation.goBack();
+ }, [navigation]);
+
+ return (
+
+
+ {
+ Alert.alert("Huddle error", err.message ?? "Failed to connect.");
+ leave();
+ }}
+ >
+
+
+
+ );
+}
+
+interface HuddleRoomProps {
+ networkId: string;
+ streamName: string;
+ onLeave: () => void;
+}
+
+function HuddleRoom({ networkId, streamName, onLeave }: HuddleRoomProps) {
+ const room = useRoomContext();
+ const network = useNetwork(networkId);
+ const { localParticipant, isMicrophoneEnabled, isCameraEnabled } =
+ useLocalParticipant();
+
+ // `withPlaceholder: true` ensures we get a tile for every participant even
+ // when their camera is off — same pattern as desktop's HuddleContent.
+ const tracks = useTracks(
+ [{ source: Track.Source.Camera, withPlaceholder: true }],
+ { onlySubscribed: false },
+ );
+
+ const toggleMic = useCallback(() => {
+ void localParticipant.setMicrophoneEnabled(!isMicrophoneEnabled);
+ }, [localParticipant, isMicrophoneEnabled]);
+
+ const toggleCamera = useCallback(() => {
+ void localParticipant.setCameraEnabled(!isCameraEnabled);
+ }, [localParticipant, isCameraEnabled]);
+
+ const leave = useCallback(async () => {
+ try {
+ await room.disconnect();
+ } finally {
+ onLeave();
+ }
+ }, [room, onLeave]);
+
+ return (
+
+
+
+
+ {streamName}
+
+
+ {tracks.length === 1
+ ? "1 participant"
+ : `${tracks.length} participants`}
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ )
+ }
+ />
+
+ ) : (
+
+ )
+ }
+ />
+ void leave()}
+ icon={}
+ />
+
+
+ );
+}
+
+interface TileGridProps {
+ tiles: TrackReferenceOrPlaceholder[];
+ humans: Human[];
+}
+
+function TileGrid({ tiles, humans }: TileGridProps) {
+ // Compute a square-ish grid: 1 → 1col, 2 → 1col (stacked), 3-4 → 2col,
+ // 5+ → 2col with scroll. Keeps each tile big enough on a phone screen.
+ const columns = tiles.length <= 1 ? 1 : 2;
+ const { width, height } = Dimensions.get("window");
+ const rows = Math.max(1, Math.ceil(tiles.length / columns));
+ const tileWidth = (width - 16) / columns - 8;
+ // Subtract approx chrome height (header + control bar ≈ 200px). This is a
+ // simple heuristic — we don't need pixel-perfect since tiles use aspectRatio.
+ const availableHeight = height - 220;
+ const tileHeight = Math.max(140, availableHeight / rows - 8);
+
+ return (
+
+ {tiles.map((tile) => (
+
+
+
+ ))}
+
+ );
+}
+
+function Tile({
+ tile,
+ humans,
+}: {
+ tile: TrackReferenceOrPlaceholder;
+ humans: Human[];
+}) {
+ const identity = tile.participant.identity;
+ const display = resolveHumanDisplay(identity, humans);
+ const name = tile.participant.name || display.displayName;
+ const initials = display.initials;
+ const isSpeaking = tile.participant.isSpeaking;
+ const muted =
+ tile.participant.getTrackPublication(Track.Source.Microphone)?.isMuted ??
+ true;
+
+ const hasVideo = isTrackReference(tile) && !tile.publication.isMuted;
+
+ return (
+
+ {hasVideo ? (
+
+ ) : (
+
+
+ {initials}
+
+
+ )}
+
+ {muted ? (
+
+ ) : (
+
+ )}
+
+ {name}
+
+
+
+ );
+}
+
+function trackKey(tile: TrackReferenceOrPlaceholder): string {
+ const sid = isTrackReference(tile) ? tile.publication.trackSid : "placeholder";
+ return `${tile.participant.identity}:${tile.source}:${sid}`;
+}
+
+interface ControlButtonProps {
+ icon: React.ReactNode;
+ label: string;
+ onPress: () => void;
+ active?: boolean;
+ tone?: "default" | "danger";
+}
+
+function ControlButton({
+ icon,
+ label,
+ onPress,
+ active = false,
+ tone = "default",
+}: ControlButtonProps) {
+ // Used purely for the visual state — destructive tone always wins so
+ // "Leave" is unmistakable regardless of toggle state.
+ return (
+
+ {icon}
+
+ );
+}
+
diff --git a/js/mobile/src/features/huddle/use-open-huddle.ts b/js/mobile/src/features/huddle/use-open-huddle.ts
new file mode 100644
index 0000000..ebd8c32
--- /dev/null
+++ b/js/mobile/src/features/huddle/use-open-huddle.ts
@@ -0,0 +1,46 @@
+import { useCallback, useState } from "react";
+import { useNavigation } from "@react-navigation/native";
+import { toast } from "sonner-native";
+import { apiClient } from "@/api/client";
+import { toUserMessage } from "@/lib/errors";
+import type { RootStackParamList } from "@/navigation/types";
+import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
+
+/**
+ * Mirrors desktop's `handleOpenHuddle` (stream-view.tsx) — fetch a fresh
+ * LiveKit token from Orion for the {network, stream} pair and hand off to
+ * the dedicated huddle screen. Joining and starting are the same operation
+ * server-side: minting a token implicitly creates/joins the room.
+ */
+export function useOpenHuddle() {
+ const navigation =
+ useNavigation>();
+ const [loading, setLoading] = useState(false);
+
+ const open = useCallback(
+ async (networkId: string, streamId: string, streamName: string) => {
+ if (loading) return;
+ setLoading(true);
+ try {
+ const { token, server_url } = await apiClient.getLivekitToken(
+ networkId,
+ streamId,
+ );
+ navigation.navigate("Huddle", {
+ networkId,
+ streamId,
+ streamName,
+ token,
+ serverUrl: server_url,
+ });
+ } catch (err) {
+ toast.error(toUserMessage(err));
+ } finally {
+ setLoading(false);
+ }
+ },
+ [navigation, loading],
+ );
+
+ return { open, loading };
+}
diff --git a/js/mobile/src/features/stream-view/StreamTopActions.tsx b/js/mobile/src/features/stream-view/StreamTopActions.tsx
index c335658..8d55602 100644
--- a/js/mobile/src/features/stream-view/StreamTopActions.tsx
+++ b/js/mobile/src/features/stream-view/StreamTopActions.tsx
@@ -1,9 +1,16 @@
-import { Pressable, Text, View } from "react-native";
-import { EllipsisVertical, Globe, Maximize2, Minimize2 } from "lucide-react-native";
+import { ActivityIndicator, Pressable, Text, View } from "react-native";
+import {
+ EllipsisVertical,
+ Globe,
+ Headphones,
+ Maximize2,
+ Minimize2,
+} from "lucide-react-native";
import type { Human, Particle } from "@/api/types";
import { parseVisibleTo } from "@/lib/stream-visibility";
import { cn } from "@/lib/utils";
import { Avatar } from "@/components/Avatar";
+import { useOpenHuddle } from "@/features/huddle/use-open-huddle";
import { useStreamPresence } from "./stream-presence-context";
interface StreamTopActionsProps {
@@ -36,6 +43,9 @@ export function StreamTopActions({
showFitToggle,
}: StreamTopActionsProps) {
const { onlineHumanIds } = useStreamPresence();
+ const { open: openHuddle, loading: huddleLoading } = useOpenHuddle();
+ const huddleCount = streamParticle.huddle_active_participants?.length ?? 0;
+ const huddleActive = huddleCount > 0;
const visibility = parseVisibleTo(streamParticle.visible_to, networkId);
const memberIds =
visibility.mode === "network"
@@ -79,6 +89,37 @@ export function StreamTopActions({
) : null}
+
+ void openHuddle(
+ networkId,
+ streamParticle.id,
+ streamParticle.properties.name,
+ )
+ }
+ disabled={huddleLoading}
+ accessibilityLabel={huddleActive ? "Join huddle" : "Start huddle"}
+ className={cn(
+ "h-8 items-center justify-center rounded-full px-2.5 flex-row gap-1",
+ huddleActive
+ ? "bg-red-500/90 active:bg-red-600"
+ : "bg-white/10 active:bg-white/20",
+ )}
+ >
+ {huddleLoading ? (
+
+ ) : (
+ <>
+
+ {huddleActive ? (
+
+ {huddleCount}
+
+ ) : null}
+ >
+ )}
+
+
{showFitToggle ? (
) : null}
- {isUnseen ? (
+ {(particle.huddle_active_participants?.length ?? 0) > 0 ? (
+
+
+
+ {particle.huddle_active_participants?.length}
+
+
+ ) : isUnseen ? (
) : null}
diff --git a/js/mobile/src/navigation/RootNavigator.tsx b/js/mobile/src/navigation/RootNavigator.tsx
index 0bbf270..1bd39b5 100644
--- a/js/mobile/src/navigation/RootNavigator.tsx
+++ b/js/mobile/src/navigation/RootNavigator.tsx
@@ -6,6 +6,7 @@ import { NetworkListScreen } from "@/features/networks/NetworkListScreen";
import { StreamListScreen } from "@/features/streams/StreamListScreen";
import { NewStreamScreen } from "@/features/streams/NewStreamScreen";
import { StreamViewScreen } from "@/features/stream-view/StreamViewScreen";
+import { HuddleScreen } from "@/features/huddle/HuddleScreen";
import { SettingsScreen } from "@/features/settings/SettingsScreen";
import { AccountScreen } from "@/features/settings/AccountScreen";
import type { RootStackParamList } from "./types";
@@ -43,6 +44,11 @@ export function RootNavigator() {
component={StreamViewScreen}
options={{ animation: "fade", gestureEnabled: false }}
/>
+
=5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+safe-push-apply@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5"
+ integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==
+ dependencies:
+ es-errors "^1.3.0"
+ isarray "^2.0.5"
+
safe-regex-test@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1"
@@ -5461,6 +6067,16 @@ scheduler@0.26.0:
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337"
integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==
+sdp-transform@^2.15.0:
+ version "2.15.0"
+ resolved "https://registry.yarnpkg.com/sdp-transform/-/sdp-transform-2.15.0.tgz#79d37a2481916f36a0534e07b32ceaa87f71df42"
+ integrity sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==
+
+sdp@^3.2.0:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.2.2.tgz#fdef97433fd61d8950cac8987803a84b11fc0aba"
+ integrity sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==
+
semver@7.7.2:
version "7.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
@@ -5522,6 +6138,25 @@ set-function-length@^1.2.2:
gopd "^1.0.1"
has-property-descriptors "^1.0.2"
+set-function-name@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985"
+ integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ functions-have-names "^1.2.3"
+ has-property-descriptors "^1.0.2"
+
+set-proto@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e"
+ integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==
+ dependencies:
+ dunder-proto "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+
setprototypeof@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
@@ -5549,6 +6184,46 @@ shell-quote@^1.6.1:
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b"
integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==
+side-channel-list@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127"
+ integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==
+ dependencies:
+ es-errors "^1.3.0"
+ object-inspect "^1.13.4"
+
+side-channel-map@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
+ integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
+
+side-channel-weakmap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
+ integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
+ side-channel-map "^1.0.1"
+
+side-channel@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
+ integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
+ dependencies:
+ es-errors "^1.3.0"
+ object-inspect "^1.13.3"
+ side-channel-list "^1.0.0"
+ side-channel-map "^1.0.1"
+ side-channel-weakmap "^1.0.2"
+
signal-exit@^3.0.2, signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
@@ -5652,6 +6327,14 @@ statuses@~2.0.2:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382"
integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
+stop-iteration-iterator@^1.0.0, stop-iteration-iterator@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad"
+ integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==
+ dependencies:
+ es-errors "^1.3.0"
+ internal-slot "^1.1.0"
+
stream-buffers@2.2.x:
version "2.2.0"
resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4"
@@ -5671,6 +6354,38 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
+string.prototype.trim@^1.2.10:
+ version "1.2.10"
+ resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81"
+ integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.2"
+ define-data-property "^1.1.4"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.5"
+ es-object-atoms "^1.0.0"
+ has-property-descriptors "^1.0.2"
+
+string.prototype.trimend@^1.0.9:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942"
+ integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.2"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+
+string.prototype.trimstart@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde"
+ integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+
strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
@@ -5862,7 +6577,7 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
-tslib@^2.1.0:
+tslib@2.8.1, tslib@^2.1.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
@@ -5882,6 +6597,58 @@ type-fest@^0.7.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
+typed-array-buffer@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536"
+ integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==
+ dependencies:
+ call-bound "^1.0.3"
+ es-errors "^1.3.0"
+ is-typed-array "^1.1.14"
+
+typed-array-byte-length@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce"
+ integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==
+ dependencies:
+ call-bind "^1.0.8"
+ for-each "^0.3.3"
+ gopd "^1.2.0"
+ has-proto "^1.2.0"
+ is-typed-array "^1.1.14"
+
+typed-array-byte-offset@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355"
+ integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.8"
+ for-each "^0.3.3"
+ gopd "^1.2.0"
+ has-proto "^1.2.0"
+ is-typed-array "^1.1.15"
+ reflect.getprototypeof "^1.0.9"
+
+typed-array-length@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d"
+ integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==
+ dependencies:
+ call-bind "^1.0.7"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ is-typed-array "^1.1.13"
+ possible-typed-array-names "^1.0.0"
+ reflect.getprototypeof "^1.0.6"
+
+typed-emitter@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-2.1.0.tgz#ca78e3d8ef1476f228f548d62e04e3d4d3fd77fb"
+ integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==
+ optionalDependencies:
+ rxjs "^7.5.2"
+
typescript@~5.9.0:
version "5.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
@@ -5892,6 +6659,16 @@ ua-parser-js@^0.7.33:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.41.tgz#9f6dee58c389e8afababa62a4a2dc22edb69a452"
integrity sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==
+unbox-primitive@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2"
+ integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==
+ dependencies:
+ call-bound "^1.0.3"
+ has-bigints "^1.0.2"
+ has-symbols "^1.1.0"
+ which-boxed-primitive "^1.1.1"
+
undici-types@~7.19.0:
version "7.19.2"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.19.2.tgz#1b67fc26d0f157a0cba3a58a5b5c1e2276b8ba2a"
@@ -5948,6 +6725,13 @@ use-sync-external-store@^1.5.0:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d"
integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
+usehooks-ts@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-3.1.1.tgz#0bb7f38f36f8219ee4509cc5e944ae610fb97656"
+ integrity sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==
+ dependencies:
+ lodash.debounce "^4.0.8"
+
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@@ -6008,6 +6792,11 @@ wcwidth@^1.0.1:
dependencies:
defaults "^1.0.3"
+web-streams-polyfill@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.3.0.tgz#2f5fe004e2c53f09f7e03aef25b892953cab99a6"
+ integrity sha512-/Gnggvj9oSrEvJbDyyPtAnxBt5fGQM2iWOKQNu7ie1OxDgK40iZpyV3TKaRiEzVj1oA1UxKnEy9XPXh6PW3eVw==
+
web-vitals@^4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7"
@@ -6018,6 +6807,13 @@ webidl-conversions@^5.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
+webrtc-adapter@9.0.5:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-9.0.5.tgz#fb036c3db06e1a0d86c264da649e130187783b3f"
+ integrity sha512-U9vjByy/sK2OMXu5mmfuZFKTMIUQe34c0JXRO+oDrxJTsntdYT2iIFwYMOV7HhMTuktcZLGf2W1N/OcSf9ssWg==
+ dependencies:
+ sdp "^3.2.0"
+
websocket-driver@>=0.5.1:
version "0.7.4"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
@@ -6032,6 +6828,14 @@ websocket-extensions@>=0.1.1:
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
+well-known-symbols@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/well-known-symbols/-/well-known-symbols-4.1.0.tgz#7f748817d7bfdd4a95395127a056ce5205910624"
+ integrity sha512-lKhCpGfPkaJnPKyep1Uj44pNmyrdupYHtxci2ThUCC/Y0px44d9BWt2dbewDif4PwOqSI6KkCdwuL22CDUj4rw==
+ dependencies:
+ get-intrinsic "^1.2.7"
+ has-symbols "^1.1.0"
+
whatwg-fetch@^3.0.0:
version "3.6.20"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70"
@@ -6046,6 +6850,46 @@ whatwg-url-without-unicode@8.0.0-3:
punycode "^2.1.1"
webidl-conversions "^5.0.0"
+which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e"
+ integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==
+ dependencies:
+ is-bigint "^1.1.0"
+ is-boolean-object "^1.2.1"
+ is-number-object "^1.1.1"
+ is-string "^1.1.1"
+ is-symbol "^1.1.1"
+
+which-builtin-type@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e"
+ integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==
+ dependencies:
+ call-bound "^1.0.2"
+ function.prototype.name "^1.1.6"
+ has-tostringtag "^1.0.2"
+ is-async-function "^2.0.0"
+ is-date-object "^1.1.0"
+ is-finalizationregistry "^1.1.0"
+ is-generator-function "^1.0.10"
+ is-regex "^1.2.1"
+ is-weakref "^1.0.2"
+ isarray "^2.0.5"
+ which-boxed-primitive "^1.1.0"
+ which-collection "^1.0.2"
+ which-typed-array "^1.1.16"
+
+which-collection@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0"
+ integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==
+ dependencies:
+ is-map "^2.0.3"
+ is-set "^2.0.3"
+ is-weakmap "^2.0.2"
+ is-weakset "^2.0.3"
+
which-typed-array@^1.1.16, which-typed-array@^1.1.2:
version "1.1.20"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.20.tgz#3fdb7adfafe0ea69157b1509f3a1cd892bd1d122"
@@ -6059,6 +6903,19 @@ which-typed-array@^1.1.16, which-typed-array@^1.1.2:
gopd "^1.2.0"
has-tostringtag "^1.0.2"
+which-typed-array@^1.1.19:
+ version "1.1.21"
+ resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.21.tgz#ea7aab68168079646af06b4a36a6f7d7b72e1c0a"
+ integrity sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.9"
+ call-bound "^1.0.4"
+ for-each "^0.3.5"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-tostringtag "^1.0.2"
+
which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"