From d82e49aab859c4c41bb1c6874d07f6927f319c73 Mon Sep 17 00:00:00 2001 From: Gigiaj Date: Tue, 15 Apr 2025 22:14:43 -0500 Subject: [PATCH] add callprovider --- src/app/pages/client/CallProvider.tsx | 157 ++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/app/pages/client/CallProvider.tsx diff --git a/src/app/pages/client/CallProvider.tsx b/src/app/pages/client/CallProvider.tsx new file mode 100644 index 00000000..2f308394 --- /dev/null +++ b/src/app/pages/client/CallProvider.tsx @@ -0,0 +1,157 @@ +import React, { createContext, useState, useContext, useMemo, useCallback, ReactNode } from 'react'; +import { logger } from 'matrix-js-sdk/lib/logger'; +// Import the transport type and action types from matrix-widget-api +import { WidgetApiToWidgetAction, ITransport, AnyWidgetAction } from 'matrix-widget-api'; // Ensure ITransport and AnyWidgetAction are imported + +// Define the shape of the context value +interface CallContextState { + activeCallRoomId: string | null; + setActiveCallRoomId: (roomId: string | null) => void; + hangUp: () => void; + // --- New additions for API interaction --- + activeApiTransport: ITransport | null; // Hold the active transport + registerActiveTransport: (roomId: string | null, transport: ITransport | null) => void; // Function for PersistentCallContainer to register/unregister + sendWidgetAction: ( + action: WidgetApiToWidgetAction | string, // Allow standard actions or custom string actions + data: T + ) => Promise; // Function for any component to send actions +} + +// Create the context +const CallContext = createContext(undefined); + +interface CallProviderProps { + children: ReactNode; +} + +// Create the Provider component +export function CallProvider({ children }: CallProviderProps) { + const [activeCallRoomId, setActiveCallRoomIdState] = useState(null); + // --- New State: Hold the transport of the currently active widget API --- + const [activeApiTransport, setActiveApiTransport] = useState(null); + // Store the room ID associated with the current active transport + const [transportRoomId, setTransportRoomId] = useState(null); + + // --- Actions --- + const setActiveCallRoomId = useCallback( + (roomId: string | null) => { + logger.debug(`CallContext: Setting activeCallRoomId to ${roomId}`); + setActiveCallRoomIdState(roomId); + // If the room being cleared is the one associated with the transport, clear the transport + if (roomId === null || roomId !== transportRoomId) { + logger.debug( + `CallContext: Clearing active transport because active room changed or was cleared.` + ); + setActiveApiTransport(null); + setTransportRoomId(null); + } + }, + [transportRoomId] + ); // Depends on transportRoomId to avoid clearing unnecessarily + + const hangUp = useCallback(() => { + logger.debug(`CallContext: Hang up called.`); + setActiveCallRoomIdState(null); + // Also clear the transport on hangup + logger.debug(`CallContext: Clearing active transport due to hangup.`); + setActiveApiTransport(null); + setTransportRoomId(null); + }, []); + + // --- New Action: Register/Unregister Transport --- + // Called by PersistentCallContainer when its widget API is ready or cleaned up + // Now accepts the roomId associated with the transport + const registerActiveTransport = useCallback( + (roomId: string | null, transport: ITransport | null) => { + if (roomId && transport) { + logger.debug(`CallContext: Registering active transport for room ${roomId}.`); + setActiveApiTransport(transport); + setTransportRoomId(roomId); + } else { + // Only clear if the transport being cleared belongs to the currently stored roomId + // or if roomId is explicitly null (global cleanup) + if (roomId === transportRoomId || roomId === null) { + logger.debug(`CallContext: Clearing active transport for room ${transportRoomId}.`); + setActiveApiTransport(null); + setTransportRoomId(null); + } else { + logger.debug( + `CallContext: Ignoring transport clear request for room ${roomId}, as current transport belongs to ${transportRoomId}.` + ); + } + } + }, + [transportRoomId] // Depends on the currently stored transportRoomId + ); + + // --- New Action: Send Action to Widget --- + // Can be called by any component within the provider + const sendWidgetAction = useCallback( + async ( + action: WidgetApiToWidgetAction | string, // Use the imported type or allow custom strings + data: T + ): Promise => { + if (!activeApiTransport) { + logger.warn( + `CallContext: Cannot send action '${action}', no active API transport registered.` + ); + return Promise.reject(new Error('No active call transport')); + } + if (!transportRoomId || transportRoomId !== activeCallRoomId) { + logger.warn( + `CallContext: Cannot send action '${action}', transport room (${transportRoomId}) does not match active call room (${activeCallRoomId}). Stale transport?` + ); + return Promise.reject(new Error('Mismatched active call transport')); + } + try { + logger.debug( + `CallContext: Sending action '${action}' via active transport (room: ${transportRoomId}) with data:`, + data + ); + // Use the transport's send method. + // The 'action' parameter should match expected Widget API actions. + // The transport handles wrapping this in the correct message format. + await activeApiTransport.send(action as AnyWidgetAction, data); // Cast action type if needed by transport method signature + } catch (error) { + logger.error(`CallContext: Error sending action '${action}':`, error); + return Promise.reject(error); + // Handle error appropriately + } + }, + [activeApiTransport, activeCallRoomId, transportRoomId] // Depends on the current transport and active room IDs + ); + + // --- Memoize Context Value --- + const contextValue = useMemo( + () => ({ + activeCallRoomId, + setActiveCallRoomId, + hangUp, + // Add new state and actions to the context value + activeApiTransport, // Keep exposing transport if direct access is ever needed, but prefer sendWidgetAction + registerActiveTransport, + sendWidgetAction, + }), + [ + activeCallRoomId, + setActiveCallRoomId, + hangUp, + activeApiTransport, + registerActiveTransport, + sendWidgetAction, + ] + ); + + // Provide the context value to children + return {children}; +} + +// --- Custom Hook --- +// Remains the same, but now returns the extended context value +export function useCallState(): CallContextState { + const context = useContext(CallContext); + if (context === undefined) { + throw new Error('useCallState must be used within a CallProvider'); + } + return context; +}