import React, { createContext, ReactNode, useEffect, useMemo, useRef } from 'react'; import { logger } from 'matrix-js-sdk/lib/logger'; import { ClientWidgetApi } from 'matrix-widget-api'; import { Box } from 'folds'; import { Outlet, useParams } from 'react-router-dom'; import { useCallState } from '../client/CallProvider'; import { createVirtualWidget, SmallWidget, getWidgetData, getWidgetUrl, } from '../../features/room/SmallWidget'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useSelectedRoom } from '../../hooks/router/useSelectedRoom'; import { useClientConfig } from '../../hooks/useClientConfig'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; interface PersistentCallContainerProps { children: ReactNode; } export const RefContext = createContext(null); export function PersistentCallContainer({ children }: PersistentCallContainerProps) { const iframeRef = useRef(null); const widgetApiRef = useRef(null); const smallWidgetRef = useRef(null); const backupIframeRef = useRef(null); const backupWidgetApiRef = useRef(null); const backupSmallWidgetRef = useRef(null); const { activeCallRoomId, isChatOpen, isCallActive, isPrimaryIframe, registerActiveClientWidgetApi, } = useCallState(); const mx = useMatrixClient(); const roomId = useSelectedRoom(); const clientConfig = useClientConfig(); const room = mx.getRoom(roomId) ?? null; const screenSize = useScreenSizeContext(); const isMobile = screenSize === ScreenSize.Mobile; const { roomIdOrAlias: viewedRoomId } = useParams(); const isViewingActiveCall = useMemo( () => activeCallRoomId !== null && activeCallRoomId === viewedRoomId, [activeCallRoomId, viewedRoomId] ); logger.info(room); const setupWidget = (widgetApiRef, smallWidgetRef, iframeRef, skipLobby) => { const cleanupRoomId = smallWidgetRef.current?.roomId; logger.debug(`PersistentCallContainer effect running. activeCallRoomId: ${activeCallRoomId}`); /** * TODO: * Need proper shutdown handling. Events from the previous widget can still come through it seems. Might need * to queue the events and then allow the join to actually occur. This will *work* for now as it does handle switching technically. * Need to look closer at it. * * Might also be able to keep the iframe alive and instead navigate to a new "room" to make the transition smoother */ const cleanup = () => { //logger.error(`CallContext: Cleaning up for previous room: ${cleanupRoomId}`); if (smallWidgetRef.current) { // smallWidgetRef.current.stopMessaging(); } // Potentially call widgetApi.stop() or similar if the API instance has it if (widgetApiRef.current) { // widgetApiRef.current.stop?.(); } //widgetApiRef.current = null; //smallWidgetRef.current = null; //if (iframeRef.current) iframeRef.current.src = 'about:blank'; }; if (activeCallRoomId && mx?.getUserId()) { if (cleanupRoomId !== activeCallRoomId && !isCallActive) { const newUrl = getWidgetUrl(mx, roomId, clientConfig.elementCallUrl ?? '', { skipLobby, returnToLobby: 'true', perParticipentE2EE: 'true', }); if (iframeRef.current && iframeRef.current.src !== newUrl.toString()) { logger.info( `PersistentCallContainer: Updating iframe src for ${activeCallRoomId} to ${newUrl.toString()}` ); cleanup(); iframeRef.current.src = newUrl.toString(); } else if (iframeRef.current && !iframeRef.current.src) { logger.info( `PersistentCallContainer: Setting initial iframe src for ${activeCallRoomId} to ${newUrl.toString()}` ); iframeRef.current.src = newUrl.toString(); } const iframeElement = iframeRef.current; if (!iframeElement) { logger.error('PersistentCallContainer: iframeRef is null, cannot setup API.'); return cleanup; } const userId = mx.getUserId() ?? ''; const app = createVirtualWidget( mx, `element-call-${activeCallRoomId}-${Date.now()}`, userId, 'Element Call', 'm.call', newUrl, false, getWidgetData(mx, activeCallRoomId, {}, { skipLobby: true }), activeCallRoomId ); logger.debug( `PersistentCallContainer: Creating new SmallWidget/API for ${activeCallRoomId}` ); const smallWidget = new SmallWidget(app); smallWidgetRef.current = smallWidget; try { const widgetApiInstance = smallWidget.startMessaging(iframeElement); widgetApiRef.current = widgetApiInstance; if (skipLobby) registerActiveClientWidgetApi(activeCallRoomId, widgetApiRef.current); widgetApiInstance.once('ready', () => { logger.info(`PersistentCallContainer: Widget for ${activeCallRoomId} is ready.`); }); } catch (error) { logger.error( `PersistentCallContainer: Error initializing widget messaging for ${activeCallRoomId}:`, error ); cleanup(); } } else { /* if (iframeRef.current && iframeRef.current.src !== 'about:blank') { logger.info('PersistentCallContainer: No active call, setting src to about:blank'); iframeRef.current.src = 'about:blank'; } */ cleanup(); } } return cleanup; }; useEffect(() => { setupWidget(widgetApiRef, smallWidgetRef, iframeRef, true); setupWidget(backupWidgetApiRef, backupSmallWidgetRef, backupIframeRef, false); }); return (