Update transport refs to allow us to properly bind listeners for media state and hangup

This commit is contained in:
GigiaJ 2025-04-29 15:28:13 -05:00
parent 1e8a69ff23
commit e76664083d

View file

@ -8,23 +8,28 @@ import React, {
useEffect, useEffect,
} from 'react'; } from 'react';
import { logger } from 'matrix-js-sdk/lib/logger'; import { logger } from 'matrix-js-sdk/lib/logger';
import { WidgetApiToWidgetAction, ITransport, WidgetApiAction } from 'matrix-widget-api'; import { WidgetApiToWidgetAction, WidgetApiAction, ClientWidgetApi } from 'matrix-widget-api';
interface MediaStatePayload { interface MediaStatePayload {
audio_enabled?: boolean; data?: {
video_enabled?: boolean; audio_enabled?: boolean;
video_enabled?: boolean;
};
} }
const WIDGET_MEDIA_STATE_UPDATE_ACTION = 'io.element.device_mute'; const WIDGET_MEDIA_STATE_UPDATE_ACTION = 'io.element.device_mute';
const WIDGET_HANGUP_ACTION = 'io.element.hangup'; const WIDGET_HANGUP_ACTION = 'im.vector.hangup';
const SET_MEDIA_STATE_ACTION = 'io.element.device_mute'; const SET_MEDIA_STATE_ACTION = 'io.element.device_mute';
interface CallContextState { interface CallContextState {
activeCallRoomId: string | null; activeCallRoomId: string | null;
setActiveCallRoomId: (roomId: string | null) => void; setActiveCallRoomId: (roomId: string | null) => void;
hangUp: () => void; hangUp: () => void;
activeApiTransport: ITransport | null; activeClientWidgetApi: ClientWidgetApi | null;
registerActiveTransport: (roomId: string | null, transport: ITransport | null) => void; registerActiveClientWidgetApi: (
roomId: string | null,
clientWidgetApi: ClientWidgetApi | null
) => void;
sendWidgetAction: <T = unknown>( sendWidgetAction: <T = unknown>(
action: WidgetApiToWidgetAction | string, action: WidgetApiToWidgetAction | string,
data: T data: T
@ -49,8 +54,10 @@ const DEFAULT_CHAT_OPENED = false;
export function CallProvider({ children }: CallProviderProps) { export function CallProvider({ children }: CallProviderProps) {
const [activeCallRoomId, setActiveCallRoomIdState] = useState<string | null>(null); const [activeCallRoomId, setActiveCallRoomIdState] = useState<string | null>(null);
const [activeApiTransport, setActiveApiTransportState] = useState<ITransport | null>(null); const [activeClientWidgetApi, setActiveClientWidgetApiState] = useState<ClientWidgetApi | null>(
const [transportRoomId, setTransportRoomId] = useState<string | null>(null); null
);
const [clientWidgetApiRoomId, setClientWidgetApiRoomId] = useState<string | null>(null);
const [isAudioEnabled, setIsAudioEnabledState] = useState<boolean>(DEFAULT_AUDIO_ENABLED); const [isAudioEnabled, setIsAudioEnabledState] = useState<boolean>(DEFAULT_AUDIO_ENABLED);
const [isVideoEnabled, setIsVideoEnabledState] = useState<boolean>(DEFAULT_VIDEO_ENABLED); const [isVideoEnabled, setIsVideoEnabledState] = useState<boolean>(DEFAULT_VIDEO_ENABLED);
@ -74,61 +81,70 @@ export function CallProvider({ children }: CallProviderProps) {
resetMediaState(); resetMediaState();
} }
if (roomId === null || roomId !== transportRoomId) { if (roomId === null || roomId !== clientWidgetApiRoomId) {
logger.warn( logger.warn(
`CallContext: Clearing active transport because active room changed to ${roomId} or was cleared.` `CallContext: Clearing active clientWidgetApi because active room changed to ${roomId} or was cleared.`
); );
setActiveApiTransportState(null); setActiveClientWidgetApiState(null);
setTransportRoomId(null); setClientWidgetApiRoomId(null);
} }
}, },
[transportRoomId, resetMediaState, activeCallRoomId] [clientWidgetApiRoomId, resetMediaState, activeCallRoomId]
); );
const hangUp = useCallback(() => { const hangUp = useCallback(() => {
logger.debug(`CallContext: Hang up called.`); logger.debug(`CallContext: Hang up called.`);
activeClientWidgetApi?.transport.send(`action:${WIDGET_HANGUP_ACTION}`, {});
setActiveCallRoomIdState(null); setActiveCallRoomIdState(null);
logger.debug(`CallContext: Clearing active transport due to hangup.`); logger.debug(`CallContext: Clearing active clientWidgetApi due to hangup.`);
setActiveApiTransportState(null); setActiveClientWidgetApiState(null);
setTransportRoomId(null); setClientWidgetApiRoomId(null);
resetMediaState(); resetMediaState();
}, [resetMediaState]); }, [resetMediaState]);
const setActiveTransport = useCallback((transport: ITransport | null, roomId: string | null) => { const setActiveClientWidgetApi = useCallback(
setActiveApiTransportState(transport); (clientWidgetApi: ClientWidgetApi | null, roomId: string | null) => {
setTransportRoomId(roomId); setActiveClientWidgetApiState(clientWidgetApi);
}, []); setClientWidgetApiRoomId(roomId);
},
[]
);
const registerActiveTransport = useCallback( const registerActiveClientWidgetApi = useCallback(
(roomId: string | null, transport: ITransport | null) => { (roomId: string | null, clientWidgetApi: ClientWidgetApi | null) => {
if (activeApiTransport && activeApiTransport !== transport) { if (activeClientWidgetApi && activeClientWidgetApi !== clientWidgetApi) {
logger.debug(`CallContext: Cleaning up listeners for previous transport instance.`); logger.debug(`CallContext: Cleaning up listeners for previous clientWidgetApi instance.`);
} }
if (roomId && transport) { if (roomId && clientWidgetApi) {
logger.debug(`CallContext: Registering active transport for room ${roomId}.`); logger.debug(`CallContext: Registering active clientWidgetApi for room ${roomId}.`);
setActiveTransport(transport, roomId); setActiveClientWidgetApi(clientWidgetApi, roomId);
} else if (roomId === transportRoomId || roomId === null) { } else if (roomId === clientWidgetApiRoomId || roomId === null) {
logger.debug(`CallContext: Clearing active transport for room ${transportRoomId}.`); logger.debug(
setActiveTransport(null, null); `CallContext: Clearing active clientWidgetApi for room ${clientWidgetApiRoomId}.`
);
setActiveClientWidgetApi(null, null);
resetMediaState(); resetMediaState();
} else { } else {
logger.debug( logger.debug(
`CallContext: Ignoring transport registration/clear request for room ${roomId}, as current transport belongs to ${transportRoomId}.` `CallContext: Ignoring clientWidgetApi registration/clear request for room ${roomId}, as current clientWidgetApi belongs to ${clientWidgetApiRoomId}.`
); );
} }
}, },
[activeApiTransport, transportRoomId, setActiveTransport, resetMediaState] [activeClientWidgetApi, clientWidgetApiRoomId, setActiveClientWidgetApi, resetMediaState]
); );
useEffect(() => { useEffect(() => {
if (!activeApiTransport || !activeCallRoomId || transportRoomId !== activeCallRoomId) { if (!activeClientWidgetApi || !activeCallRoomId || clientWidgetApiRoomId !== activeCallRoomId) {
return; return;
} }
const transport = activeApiTransport; const clientWidgetApi = activeClientWidgetApi;
const handleHangup = (ev: CustomEvent) => { const handleHangup = (ev: CustomEvent) => {
ev.preventDefault();
clientWidgetApi.transport.reply(ev.detail, {});
logger.warn( logger.warn(
`CallContext: Received hangup action from widget in room ${activeCallRoomId}.`, `CallContext: Received hangup action from widget in room ${activeCallRoomId}.`,
ev ev
@ -142,7 +158,7 @@ export function CallProvider({ children }: CallProviderProps) {
`CallContext: Received media state update from widget in room ${activeCallRoomId}:`, `CallContext: Received media state update from widget in room ${activeCallRoomId}:`,
ev.detail ev.detail
); );
const { audio_enabled, video_enabled } = ev.detail; const { audio_enabled, video_enabled } = ev.detail.data;
if (typeof audio_enabled === 'boolean' && audio_enabled !== isAudioEnabled) { if (typeof audio_enabled === 'boolean' && audio_enabled !== isAudioEnabled) {
logger.debug(`CallContext: Updating audio enabled state from widget: ${audio_enabled}`); logger.debug(`CallContext: Updating audio enabled state from widget: ${audio_enabled}`);
setIsAudioEnabledState(audio_enabled); setIsAudioEnabledState(audio_enabled);
@ -153,21 +169,25 @@ export function CallProvider({ children }: CallProviderProps) {
} }
}; };
logger.debug(`CallContext: Setting up listeners for transport in room ${activeCallRoomId}`); logger.debug(
transport.on(`action:${WIDGET_HANGUP_ACTION}`, handleHangup); // Use standard hangup action name `CallContext: Setting up listeners for clientWidgetApi in room ${activeCallRoomId}`
transport.on(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate); );
clientWidgetApi.on(`action:${WIDGET_HANGUP_ACTION}`, handleHangup); // Use standard hangup action name
clientWidgetApi.on(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate);
return () => { return () => {
logger.debug(`CallContext: Cleaning up listeners for transport in room ${activeCallRoomId}`); logger.debug(
if (transport) { `CallContext: Cleaning up listeners for clientWidgetApi in room ${activeCallRoomId}`
transport.off(`action:${WIDGET_HANGUP_ACTION}`, handleHangup); );
transport.off(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate); if (clientWidgetApi) {
clientWidgetApi.off(`action:${WIDGET_HANGUP_ACTION}`, handleHangup);
clientWidgetApi.off(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate);
} }
}; };
}, [ }, [
activeApiTransport, activeClientWidgetApi,
activeCallRoomId, activeCallRoomId,
transportRoomId, clientWidgetApiRoomId,
hangUp, hangUp,
isChatOpen, isChatOpen,
isAudioEnabled, isAudioEnabled,
@ -176,30 +196,30 @@ export function CallProvider({ children }: CallProviderProps) {
const sendWidgetAction = useCallback( const sendWidgetAction = useCallback(
async <T = unknown,>(action: WidgetApiToWidgetAction | string, data: T): Promise<void> => { async <T = unknown,>(action: WidgetApiToWidgetAction | string, data: T): Promise<void> => {
if (!activeApiTransport) { if (!activeClientWidgetApi) {
logger.warn( logger.warn(
`CallContext: Cannot send action '${action}', no active API transport registered.` `CallContext: Cannot send action '${action}', no active API clientWidgetApi registered.`
); );
return Promise.reject(new Error('No active call transport')); return Promise.reject(new Error('No active call clientWidgetApi'));
} }
if (!transportRoomId || transportRoomId !== activeCallRoomId) { if (!clientWidgetApiRoomId || clientWidgetApiRoomId !== activeCallRoomId) {
logger.warn( logger.debug(
`CallContext: Cannot send action '${action}', transport room (${transportRoomId}) does not match active call room (${activeCallRoomId}). Stale transport?` `CallContext: Cannot send action '${action}', clientWidgetApi room (${clientWidgetApiRoomId}) does not match active call room (${activeCallRoomId}). Stale clientWidgetApi?`
); );
return Promise.reject(new Error('Mismatched active call transport')); return Promise.reject(new Error('Mismatched active call clientWidgetApi'));
} }
try { try {
logger.debug( logger.debug(
`CallContext: Sending action '${action}' via active transport (room: ${transportRoomId}) with data:`, `CallContext: Sending action '${action}' via active clientWidgetApi (room: ${clientWidgetApiRoomId}) with data:`,
data data
); );
await activeApiTransport.send<T>(action as WidgetApiAction, data); await activeClientWidgetApi.transport.send(action as WidgetApiAction, data);
} catch (error) { } catch (error) {
logger.error(`CallContext: Error sending action '${action}':`, error); logger.error(`CallContext: Error sending action '${action}':`, error);
throw error; throw error;
} }
}, },
[activeApiTransport, activeCallRoomId, transportRoomId] [activeClientWidgetApi, activeCallRoomId, clientWidgetApiRoomId]
); );
const toggleAudio = useCallback(async () => { const toggleAudio = useCallback(async () => {
@ -246,8 +266,8 @@ export function CallProvider({ children }: CallProviderProps) {
activeCallRoomId, activeCallRoomId,
setActiveCallRoomId, setActiveCallRoomId,
hangUp, hangUp,
activeApiTransport, activeClientWidgetApi,
registerActiveTransport, registerActiveClientWidgetApi,
sendWidgetAction, sendWidgetAction,
isChatOpen, isChatOpen,
isAudioEnabled, isAudioEnabled,
@ -260,8 +280,8 @@ export function CallProvider({ children }: CallProviderProps) {
activeCallRoomId, activeCallRoomId,
setActiveCallRoomId, setActiveCallRoomId,
hangUp, hangUp,
activeApiTransport, activeClientWidgetApi,
registerActiveTransport, registerActiveClientWidgetApi,
sendWidgetAction, sendWidgetAction,
isChatOpen, isChatOpen,
isAudioEnabled, isAudioEnabled,