diff --git a/app.json b/app.json index 3deb4fb..0177755 100644 --- a/app.json +++ b/app.json @@ -49,8 +49,7 @@ "backgroundColor": "#ffffff" } ], - "expo-font", - "expo-web-browser" + "expo-font" ], "experiments": { "typedRoutes": true diff --git a/app/(start)/index.tsx b/app/(start)/index.tsx index d6efae9..e272a29 100644 --- a/app/(start)/index.tsx +++ b/app/(start)/index.tsx @@ -1,6 +1,6 @@ -import { ConnectionDetails, fetchToken } from '@/hooks/useConnectionDetails'; +import { useConnection } from '@/hooks/useConnection'; import { useRouter } from 'expo-router'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { StyleSheet, View, @@ -12,37 +12,18 @@ import { export default function StartScreen() { const router = useRouter(); - - let [isConnecting, setConnecting] = useState(false); - let [connectionDetails, setConnectionDetails] = useState< - ConnectionDetails | undefined - >(undefined); - - // Fetch token when we're connecting. - useEffect(() => { - if (isConnecting) { - fetchToken().then((details) => { - console.log(details); - setConnectionDetails(details); - if (!details) { - setConnecting(false); - } - }); - } - }, [isConnecting]); + const { isConnectionActive, connect } = useConnection(); // Navigate to Assistant screen when we have the connection details. useEffect(() => { - if (isConnecting && connectionDetails) { - setConnecting(false); - setConnectionDetails(undefined); + if (isConnectionActive) { router.navigate('../assistant'); } - }, [isConnecting, router, connectionDetails]); + }, [isConnectionActive, router]); let connectText: string; - if (isConnecting) { + if (isConnectionActive) { connectText = 'Connecting'; } else { connectText = 'Start Voice Assistant'; @@ -58,13 +39,13 @@ export default function StartScreen() { { - setConnecting(true); + connect(); }} style={styles.button} activeOpacity={0.7} - disabled={isConnecting} // Disable button while loading + disabled={isConnectionActive} // Disable button while loading > - {isConnecting ? ( + {isConnectionActive ? ( - - - - - - + + + + + + + + + ); } diff --git a/app/assistant/index.tsx b/app/assistant/index.tsx index 54caa77..35cdeb8 100644 --- a/app/assistant/index.tsx +++ b/app/assistant/index.tsx @@ -10,22 +10,25 @@ import { import React, { useCallback, useEffect, useState } from 'react'; import { AudioSession, - LiveKitRoom, useIOSAudioManagement, useLocalParticipant, useParticipantTracks, useRoomContext, VideoTrack, } from '@livekit/react-native'; -import { useConnectionDetails } from '@/hooks/useConnectionDetails'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import ControlBar from './ui/ControlBar'; import ChatBar from './ui/ChatBar'; import ChatLog from './ui/ChatLog'; import AgentVisualization from './ui/AgentVisualization'; -import useDataStreamTranscriptions from '@/hooks/useDataStreamTranscriptions'; import { Track } from 'livekit-client'; +import { + TrackReference, + useSessionMessages, + useTrackToggle, +} from '@livekit/components-react'; +import { useConnection } from '@/hooks/useConnection'; export default function AssistantScreen() { // Start the audio session first. @@ -40,27 +43,18 @@ export default function AssistantScreen() { }; }, []); - const connectionDetails = useConnectionDetails(); - return ( - - - + ); } const RoomView = () => { const router = useRouter(); - + const connection = useConnection(); const room = useRoomContext(); + useIOSAudioManagement(room, true); const { @@ -79,45 +73,41 @@ const RoomView = () => { const localVideoTrack = localCameraTrack && isCameraEnabled - ? { + ? ({ participant: localParticipant, publication: localCameraTrack, source: Track.Source.Camera, - } + } satisfies TrackReference) : localScreenShareTrack.length > 0 && isScreenShareEnabled ? localScreenShareTrack[0] : null; - // Transcriptions - const transcriptionState = useDataStreamTranscriptions(); - const addTranscription = transcriptionState.addTranscription; + // Messages + const { messages, send } = useSessionMessages(); const [isChatEnabled, setChatEnabled] = useState(false); const [chatMessage, setChatMessage] = useState(''); const onChatSend = useCallback( (message: string) => { - addTranscription(localParticipantIdentity, message); + send(message); setChatMessage(''); }, - [localParticipantIdentity, addTranscription, setChatMessage] + [setChatMessage, send] ); // Control callbacks - const onMicClick = useCallback(() => { - localParticipant.setMicrophoneEnabled(!isMicrophoneEnabled); - }, [isMicrophoneEnabled, localParticipant]); - const onCameraClick = useCallback(() => { - localParticipant.setCameraEnabled(!isCameraEnabled); - }, [isCameraEnabled, localParticipant]); - const onScreenShareClick = useCallback(() => { - localParticipant.setScreenShareEnabled(!isScreenShareEnabled); - }, [isScreenShareEnabled, localParticipant]); + const micToggle = useTrackToggle({ source: Track.Source.Microphone }); + const cameraToggle = useTrackToggle({ source: Track.Source.Camera }); + const screenShareToggle = useTrackToggle({ + source: Track.Source.ScreenShare, + }); const onChatClick = useCallback(() => { setChatEnabled(!isChatEnabled); }, [isChatEnabled, setChatEnabled]); const onExitClick = useCallback(() => { + connection.disconnect(); router.back(); - }, [router]); + }, [connection, router]); // Layout positioning const [containerWidth, setContainerWidth] = useState( @@ -159,10 +149,7 @@ const RoomView = () => { }} > - + { isCameraEnabled, isScreenShareEnabled, isChatEnabled, - onMicClick, - onCameraClick, + onMicClick: micToggle.toggle, + onCameraClick: cameraToggle.toggle, onChatClick, - onScreenShareClick, + onScreenShareClick: screenShareToggle.toggle, onExitClick, }} /> diff --git a/app/assistant/ui/AgentVisualization.tsx b/app/assistant/ui/AgentVisualization.tsx index bfb61c3..df68e67 100644 --- a/app/assistant/ui/AgentVisualization.tsx +++ b/app/assistant/ui/AgentVisualization.tsx @@ -1,4 +1,4 @@ -import { useVoiceAssistant } from '@livekit/components-react'; +import { useAgent } from '@livekit/components-react'; import { BarVisualizer, VideoTrack } from '@livekit/react-native'; import React, { useCallback, useState } from 'react'; import { @@ -16,17 +16,19 @@ type AgentVisualizationProps = { const barSize = 0.2; export default function AgentVisualization({ style }: AgentVisualizationProps) { - const { state, audioTrack, videoTrack } = useVoiceAssistant(); + const { state, microphoneTrack, cameraTrack } = useAgent(); const [barWidth, setBarWidth] = useState(0); const [barBorderRadius, setBarBorderRadius] = useState(0); + const layoutCallback = useCallback((event: LayoutChangeEvent) => { const { x, y, width, height } = event.nativeEvent.layout; console.log(x, y, width, height); setBarWidth(barSize * height); setBarBorderRadius(barSize * height); }, []); - let videoView = videoTrack ? ( - + + let videoView = cameraTrack ? ( + ) : null; return ( @@ -40,7 +42,7 @@ export default function AgentVisualization({ style }: AgentVisualizationProps) { barColor: '#FFFFFF', barBorderRadius: barBorderRadius, }} - trackRef={audioTrack} + trackRef={microphoneTrack} style={styles.barVisualizer} /> diff --git a/app/assistant/ui/ChatLog.tsx b/app/assistant/ui/ChatLog.tsx index 5869abc..1326ece 100644 --- a/app/assistant/ui/ChatLog.tsx +++ b/app/assistant/ui/ChatLog.tsx @@ -1,5 +1,7 @@ -import { Transcription } from '@/hooks/useDataStreamTranscriptions'; -import { useLocalParticipant } from '@livekit/components-react'; +import { + ReceivedMessage, + useLocalParticipant, +} from '@livekit/components-react'; import { useCallback } from 'react'; import { ListRenderItemInfo, @@ -14,28 +16,30 @@ import Animated, { LinearTransition } from 'react-native-reanimated'; export type ChatLogProps = { style: StyleProp; - transcriptions: Transcription[]; + messages: ReceivedMessage[]; }; -export default function ChatLog({ style, transcriptions }: ChatLogProps) { +export default function ChatLog({ + style, + messages: transcriptions, +}: ChatLogProps) { const { localParticipant } = useLocalParticipant(); - const localParticipantIdentity = localParticipant.identity; const renderItem = useCallback( - ({ item }: ListRenderItemInfo) => { - const isLocalUser = item.identity === localParticipantIdentity; + ({ item }: ListRenderItemInfo) => { + const isLocalUser = item.from === localParticipant; if (isLocalUser) { - return ; + return ; } else { - return ; + return ; } }, - [localParticipantIdentity] + [localParticipant] ); return ( void; + disconnect: () => void; +} + +const ConnectionContext = createContext({ + isConnectionActive: false, + connect: () => {}, + disconnect: () => {}, +}); + +export function useConnection() { + const ctx = useContext(ConnectionContext); + if (!ctx) { + throw new Error('useConnection must be used within a ConnectionProvider'); + } + return ctx; +} + +interface ConnectionProviderProps { + children: React.ReactNode; +} + +export function ConnectionProvider({ children }: ConnectionProviderProps) { + const [isConnectionActive, setIsConnectionActive] = useState(false); + + const tokenSource = useMemo(() => { + if (sandboxID) { + return TokenSource.sandboxTokenServer(sandboxID) + } else { + return TokenSource.literal( + { + serverUrl: hardcodedUrl, + participantToken: hardcodedToken, + } satisfies TokenSourceResponseObject + ) + } + }, [sandboxID, hardcodedUrl, hardcodedToken]) + + const session = useSession( + tokenSource, + agentName ? { agentName } : undefined + ); + + const { start: startSession, end: endSession } = session; + + const value = useMemo(() => { + return { + isConnectionActive, + connect: () => { + setIsConnectionActive(true); + startSession(); + }, + disconnect: () => { + setIsConnectionActive(false); + endSession(); + }, + }; + }, [startSession, endSession, isConnectionActive]); + + return ( + + {children} + + ); +} diff --git a/hooks/useConnectionDetails.ts b/hooks/useConnectionDetails.ts deleted file mode 100644 index 60597f8..0000000 --- a/hooks/useConnectionDetails.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useEffect, useState } from 'react'; - -// TODO: Add your Sandbox ID here -const sandboxID = ''; -const tokenEndpoint = - 'https://cloud-api.livekit.io/api/sandbox/connection-details'; - -// For use without a token server. -const hardcodedUrl = ''; -const hardcodedToken = ''; - -/** - * Retrieves a LiveKit token. - * - * Currently configured to use LiveKit's Sandbox token server. - * When building an app for production, you should use your own token server. - */ -export function useConnectionDetails(): ConnectionDetails | undefined { - const [details, setDetails] = useState(() => { - return undefined; - }); - - useEffect(() => { - fetchToken().then(details => { - setDetails(details); - }); - }, []); - - return details; -} - -export async function fetchToken() : Promise { - - if (!sandboxID) { - return { - url: hardcodedUrl, - token: hardcodedToken, - }; - } - const fetchToken = async () => { - if (!sandboxID) { - return undefined; - } - const response = await fetch(tokenEndpoint, { - headers: { 'X-Sandbox-ID': sandboxID }, - }); - const json = await response.json(); - - if (json.serverUrl && json.participantToken) { - return { - url: json.serverUrl, - token: json.participantToken, - }; - } else { - return undefined; - } - }; - return fetchToken(); -} - -export type ConnectionDetails = { - url: string; - token: string; -}; diff --git a/hooks/useDataStreamTranscriptions.ts b/hooks/useDataStreamTranscriptions.ts deleted file mode 100644 index e77d37f..0000000 --- a/hooks/useDataStreamTranscriptions.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { useRoomContext, useVoiceAssistant } from "@livekit/components-react" -import { TextStreamReader, TranscriptionSegment } from "livekit-client" -import { useCallback, useEffect, useState } from "react" - -export type Transcription = { - identity: string, - segment: TranscriptionSegment, -} - -export type TranscriptionsState = { - transcriptions: Transcription[], - addTranscription: (identity: string, message: string) => void, -} - -export default function useDataStreamTranscriptions(): TranscriptionsState { - const room = useRoomContext(); - const { agent } = useVoiceAssistant(); - const agentIdentity = agent?.identity; - - const [transcriptionMap] = useState>(new Map()); - const [transcriptions, setTranscriptions] = useState([]); - - const mergeTranscriptions = useCallback((merge: Transcription[]) => { - for (const transcription of merge) { - const existing = transcriptionMap.get(transcription.segment.id); - transcriptionMap.set(transcription.segment.id, { - identity: transcription.identity, - segment: mergeTranscriptionSegment(existing?.segment, transcription.segment) - }); - } - - const sortedTranscriptions = Array.from(transcriptionMap.values()) - .sort((a, b) => b.segment.firstReceivedTime - a.segment.firstReceivedTime); - - setTranscriptions(sortedTranscriptions); - }, [transcriptionMap, setTranscriptions]); - - const addTranscription = useCallback((identity: string, message: string) => { - const now = Date.now() - const newTranscription: Transcription = { - identity, - segment: { - id: crypto.randomUUID(), - text: message, - language: '', - startTime: now, - endTime: now, - final: true, - firstReceivedTime: now, - lastReceivedTime: now, - } - } - mergeTranscriptions([newTranscription]); - - // Send message to agent - if (agentIdentity) { - room.localParticipant.sendText(message, { - topic: 'lk.chat', - destinationIdentities: [agentIdentity], - }); - } - }, [mergeTranscriptions, agentIdentity]); - - useEffect(() => { - room.registerTextStreamHandler("lk.transcription", ( - reader: TextStreamReader, - participantInfo: { identity: string }, - ) => { - - const segment = createTranscriptionSegment(reader.info.attributes); - - let text = ''; - - const readFunc = async () => { - for await (const chunk of reader) { - text += chunk; - const updatedSegment = { - ...segment, - text, - lastReceivedTime: Date.now(), - }; - mergeTranscriptions([{ - identity: participantInfo.identity, - segment: updatedSegment, - }]); - } - - const finalSegment = { - ...segment, - text, - final: true, - } - - mergeTranscriptions([{ - identity: participantInfo.identity, - segment: finalSegment, - }]); - }; - - readFunc(); - }); - - return () => { - room.unregisterTextStreamHandler("lk.transcription"); - } - }, [room]); - - return { - transcriptions, - addTranscription - } -} - -const createTranscriptionSegment = (attributes?: Record): TranscriptionSegment => { - const now = Date.now() - return { - id: attributes?.['lk.segment_id'] ?? '', - text: '', - language: '', - startTime: now, - endTime: now, - final: (attributes?.['lk.transcription.final'] ?? false) === 'true', - firstReceivedTime: now, - lastReceivedTime: now, - } -} - -const mergeTranscriptionSegment = (existing: TranscriptionSegment | undefined, newSegment: TranscriptionSegment): TranscriptionSegment => { - if (!existing) { - return newSegment; - } - - if (existing.id != newSegment.id) { - return existing; - } - - return { - ...existing, - text: newSegment.text, - language: newSegment.language, - final: newSegment.final, - endTime: newSegment.endTime, - lastReceivedTime: newSegment.lastReceivedTime, - } -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d78fdc3..796b234 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,26 +10,22 @@ "dependencies": { "@config-plugins/react-native-webrtc": "13.0.0", "@expo/vector-icons": "^15.0.3", - "@livekit/components-react": "^2.8.1", - "@livekit/react-native": "^2.9.0", + "@livekit/components-react": "^2.9.15", + "@livekit/react-native": "^2.9.5", "@livekit/react-native-expo-plugin": "^1.0.1", "@livekit/react-native-webrtc": "^137.0.0", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "expo": "^54.0.22", - "expo-blur": "~15.0.7", "expo-constants": "~18.0.10", "expo-dev-client": "~6.0.16", "expo-font": "~14.0.9", - "expo-haptics": "~15.0.7", - "expo-linking": "~8.0.8", "expo-router": "~6.0.14", "expo-splash-screen": "~31.0.10", "expo-status-bar": "~3.0.8", "expo-symbols": "~1.0.7", "expo-system-ui": "~6.0.8", - "expo-web-browser": "~15.0.9", - "livekit-client": "^2.11.2", + "livekit-client": "^2.15.16", "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.5", @@ -39,6 +35,7 @@ "react-native-screens": "~4.16.0", "react-native-web": "^0.21.0", "react-native-webview": "13.15.0", + "react-native-worklets": "0.5.1", "typescript": "5.0.4" }, "devDependencies": { @@ -2774,9 +2771,9 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", - "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" @@ -3430,9 +3427,9 @@ } }, "node_modules/@livekit/components-core": { - "version": "0.12.9", - "resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.9.tgz", - "integrity": "sha512-bwrZsHf6GaHIO+lLyA6Yps1STTX9YIeL3ixwt+Ufi88OgkNYdp41Ug8oeVDlf7tzdxa+r3Xkfaj/qvIG84Yo6A==", + "version": "0.12.11", + "resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.11.tgz", + "integrity": "sha512-3GYEuuZp9nbLdGx0YcwEYimHY/m0hj6Bl5CELKLsJ6Q3PJ2m4l2uacVE9Ff+y1kqNHPKdxhAmiC8RiESo6TTaw==", "license": "Apache-2.0", "dependencies": { "@floating-ui/dom": "1.6.13", @@ -3443,7 +3440,7 @@ "node": ">=18" }, "peerDependencies": { - "livekit-client": "^2.13.3", + "livekit-client": "^2.15.14", "tslib": "^2.6.2" } }, @@ -3461,13 +3458,15 @@ } }, "node_modules/@livekit/components-react": { - "version": "2.9.14", - "resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.9.14.tgz", - "integrity": "sha512-fQ3t4PdcM+AORo62FWmJcfqWe7ODwVaU4nsqxse+fp6L5a+0K2uMD7yQ2jrutXIaUQigU/opzTUxPcpdk9+0ow==", + "version": "2.9.16", + "resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.9.16.tgz", + "integrity": "sha512-7HqsVxmsIjhnyPGFskO0UL0Vf5WKAnIsukHcRp+hYTA4hIT99Dlcnhm/b0zfNpVaGafP2WV32HLeRMYzBrfMPA==", "license": "Apache-2.0", "dependencies": { - "@livekit/components-core": "0.12.9", + "@livekit/components-core": "0.12.11", "clsx": "2.1.1", + "events": "^3.3.0", + "jose": "^6.0.12", "usehooks-ts": "3.1.1" }, "engines": { @@ -3475,7 +3474,7 @@ }, "peerDependencies": { "@livekit/krisp-noise-filter": "^0.2.12 || ^0.3.0", - "livekit-client": "^2.13.3", + "livekit-client": "^2.15.14", "react": ">=18", "react-dom": ">=18", "tslib": "^2.6.2" @@ -3493,38 +3492,42 @@ "license": "Apache-2.0" }, "node_modules/@livekit/protocol": { - "version": "1.39.3", - "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.39.3.tgz", - "integrity": "sha512-hfOnbwPCeZBEvMRdRhU2sr46mjGXavQcrb3BFRfG+Gm0Z7WUSeFdy5WLstXJzEepz17Iwp/lkGwJ4ZgOOYfPuA==", + "version": "1.42.2", + "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.42.2.tgz", + "integrity": "sha512-0jeCwoMJKcwsZICg5S6RZM4xhJoF78qMvQELjACJQn6/VB+jmiySQKOSELTXvPBVafHfEbMlqxUw2UR1jTXs2g==", "license": "Apache-2.0", "dependencies": { "@bufbuild/protobuf": "^1.10.0" } }, "node_modules/@livekit/react-native": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@livekit/react-native/-/react-native-2.9.0.tgz", - "integrity": "sha512-6DIj8Tgcldc/s+AxUE2SXUVVtSlWN80c62LwOonhK+FWqbPSFPMUMAunaOcf4+/pZArBaL7Vahd9AczmG/leug==", + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@livekit/react-native/-/react-native-2.9.5.tgz", + "integrity": "sha512-APQaUwStw1q/1SPisSH0pxbfuX+uJPjFq//99xK+ijl/Vkcnop6uBLaLWrvC5H475xi2DOyK+lKmlEfQX4mXeQ==", "license": "Apache-2.0", + "workspaces": [ + "example" + ], "dependencies": { "@livekit/components-react": "^2.8.1", + "@livekit/mutex": "^1.1.1", "array.prototype.at": "^1.1.1", + "base64-js": "1.5.1", "event-target-shim": "6.0.2", "events": "^3.3.0", "loglevel": "^1.8.0", "promise.allsettled": "^1.0.5", - "react-native-quick-base64": "2.1.1", "react-native-url-polyfill": "^1.3.0", "typed-emitter": "^2.1.0", "web-streams-polyfill": "^4.1.0", "well-known-symbols": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { - "@livekit/react-native-webrtc": "^137.0.0", - "livekit-client": "^2.15.4", + "@livekit/react-native-webrtc": "^137.0.2", + "livekit-client": "^2.15.8", "react": "*", "react-native": "*" } @@ -3542,9 +3545,9 @@ } }, "node_modules/@livekit/react-native-webrtc": { - "version": "137.0.0", - "resolved": "https://registry.npmjs.org/@livekit/react-native-webrtc/-/react-native-webrtc-137.0.0.tgz", - "integrity": "sha512-06uy99sUydGMghS5o2Vh09WAKCH3IcHA6Gr3MjnUPlkEZiLbt+9eX8kNfs8WhqAZNBB4KZAQKo+/shRbFUPvBA==", + "version": "137.0.2", + "resolved": "https://registry.npmjs.org/@livekit/react-native-webrtc/-/react-native-webrtc-137.0.2.tgz", + "integrity": "sha512-0aXYATcBraOMDTteKzmfH5ICNHw8xFyMPHmhKg14+94fAGZ2hGjdHZUSkzL14+e508W486aIAmbXipuSQCCJgA==", "license": "MIT", "peer": true, "dependencies": { @@ -7845,6 +7848,7 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -8352,17 +8356,6 @@ "react-native": "*" } }, - "node_modules/expo-blur": { - "version": "15.0.7", - "resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-15.0.7.tgz", - "integrity": "sha512-SugQQbQd+zRPy8z2G5qDD4NqhcD7srBF7fN7O7yq6q7ZFK59VWvpDxtMoUkmSfdxgqONsrBN/rLdk00USADrMg==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, "node_modules/expo-constants": { "version": "18.0.10", "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.10.tgz", @@ -8453,15 +8446,6 @@ "react-native": "*" } }, - "node_modules/expo-haptics": { - "version": "15.0.7", - "resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-15.0.7.tgz", - "integrity": "sha512-7flWsYPrwjJxZ8x82RiJtzsnk1Xp9ahnbd9PhCy3NnsemyMApoWIEUr4waPqFr80DtiLZfhD9VMLL1CKa8AImQ==", - "license": "MIT", - "peerDependencies": { - "expo": "*" - } - }, "node_modules/expo-json-utils": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.15.0.tgz", @@ -8864,16 +8848,6 @@ "expo": "*" } }, - "node_modules/expo-web-browser": { - "version": "15.0.9", - "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-15.0.9.tgz", - "integrity": "sha512-Dj8kNFO+oXsxqCDNlUT/GhOrJnm10kAElH++3RplLydogFm5jTzXYWDEeNIDmV+F+BzGYs+sIhxiBf7RyaxXZg==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react-native": "*" - } - }, "node_modules/expo/node_modules/@expo/cli": { "version": "54.0.15", "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-54.0.15.tgz", @@ -11504,6 +11478,15 @@ "integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==", "license": "MIT" }, + "node_modules/jose": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.2.tgz", + "integrity": "sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11991,15 +11974,16 @@ "license": "MIT" }, "node_modules/livekit-client": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.15.4.tgz", - "integrity": "sha512-DHNuCibSGX0xOxWvVK4VVpadNm1jtUQqU3HdDnoZYJ05/iO/DWzi1F7JPsnvY3RDw0eBeEz32KfcKtLq0kB/9w==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.16.0.tgz", + "integrity": "sha512-2iYJ4dok17yV5CGeaY1yaFvz7rMuNUmXN1+nXvhUrkxTS/RcuteWTpxwrgLG/Vl1yxkf/YquVQ7bbRwFye20CA==", "license": "Apache-2.0", "peer": true, "dependencies": { "@livekit/mutex": "1.1.1", - "@livekit/protocol": "1.39.3", + "@livekit/protocol": "1.42.2", "events": "^3.3.0", + "jose": "^6.1.0", "loglevel": "^1.9.2", "sdp-transform": "^2.15.0", "ts-debounce": "^4.0.0", @@ -14049,19 +14033,6 @@ "react-native": "*" } }, - "node_modules/react-native-quick-base64": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-native-quick-base64/-/react-native-quick-base64-2.1.1.tgz", - "integrity": "sha512-/L+SaapDLcLcHA3Kj0/cvEhFS/oLXyD6DUXwdckTR/75bzUjqf1FrusUTvJho/lzXnCR1VtBGn+EEvrLmkDTmw==", - "license": "MIT", - "dependencies": { - "base64-js": "^1.5.1" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, "node_modules/react-native-reanimated": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.3.tgz", @@ -14178,9 +14149,9 @@ } }, "node_modules/react-native-worklets": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.2.tgz", - "integrity": "sha512-lCzmuIPAK/UaOJYEPgYpVqrsxby1I54f7PyyZUMEO04nwc00CDrCvv9lCTY1daLHYTF8lS3f9zlzErfVsIKqkA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz", + "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==", "license": "MIT", "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", diff --git a/package.json b/package.json index 06f09fd..6141217 100644 --- a/package.json +++ b/package.json @@ -19,26 +19,22 @@ "dependencies": { "@config-plugins/react-native-webrtc": "13.0.0", "@expo/vector-icons": "^15.0.3", - "@livekit/components-react": "^2.8.1", - "@livekit/react-native": "^2.9.0", + "@livekit/components-react": "^2.9.15", + "@livekit/react-native": "^2.9.5", "@livekit/react-native-expo-plugin": "^1.0.1", "@livekit/react-native-webrtc": "^137.0.0", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "expo": "^54.0.22", - "expo-blur": "~15.0.7", "expo-constants": "~18.0.10", "expo-dev-client": "~6.0.16", "expo-font": "~14.0.9", - "expo-haptics": "~15.0.7", - "expo-linking": "~8.0.8", "expo-router": "~6.0.14", "expo-splash-screen": "~31.0.10", "expo-status-bar": "~3.0.8", "expo-symbols": "~1.0.7", "expo-system-ui": "~6.0.8", - "expo-web-browser": "~15.0.9", - "livekit-client": "^2.11.2", + "livekit-client": "^2.15.16", "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.5", @@ -48,6 +44,7 @@ "react-native-screens": "~4.16.0", "react-native-web": "^0.21.0", "react-native-webview": "13.15.0", + "react-native-worklets": "0.5.1", "typescript": "5.0.4" }, "devDependencies": { diff --git a/taskfile.yaml b/taskfile.yaml index 48a5f8c..4988dc5 100644 --- a/taskfile.yaml +++ b/taskfile.yaml @@ -1,4 +1,4 @@ -version: "3" +version: "4" output: interleaved dotenv: [".env.local"] @@ -6,7 +6,7 @@ tasks: post_create: desc: "Runs after this template is instantiated as a Sandbox or Bootstrap" vars: - TOKEN_FILE: "hooks/useConnectionDetails.ts" + TOKEN_FILE: "hooks/useConnection.ts" cmds: - echo -e "Setting up sandbox..." - platforms: [darwin]