diff --git a/.changeset/call-auto-leave-config.md b/.changeset/call-auto-leave-config.md new file mode 100644 index 000000000..315122e7d --- /dev/null +++ b/.changeset/call-auto-leave-config.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +Add optional `callOptions.autoLeaveWhenOthersLeft` to client config so admins can opt out of Element Call's default auto-leave behavior diff --git a/src/app/hooks/useCallEmbed.ts b/src/app/hooks/useCallEmbed.ts index aa3466193..d2ac8fa93 100644 --- a/src/app/hooks/useCallEmbed.ts +++ b/src/app/hooks/useCallEmbed.ts @@ -5,6 +5,7 @@ import { useSetAtom } from 'jotai'; import * as Sentry from '@sentry/react'; import { CallEmbed, + CallOptions, ElementCallThemeKind, ElementWidgetActions, useClientWidgetApiEvent, @@ -17,6 +18,7 @@ import { CallControlState } from '../plugins/call/CallControlState'; import { useCallMembersChange, useCallSession } from './useCall'; import { CallPreferences } from '../state/callPreferences'; import { createDebugLogger } from '../utils/debugLogger'; +import { useClientConfig } from './useClientConfig'; const debugLog = createDebugLogger('useCallEmbed'); @@ -46,14 +48,15 @@ export const createCallEmbed = ( dm: boolean, themeKind: ElementCallThemeKind, container: HTMLElement, - pref?: CallPreferences + pref?: CallPreferences, + callOptions?: CallOptions ): CallEmbed => { const rtcSession = mx.matrixRTC.getRoomSession(room); const ongoing = MatrixRTCSession.sessionMembershipsForRoom(room, rtcSession.sessionDescription).length > 0; const intent = CallEmbed.getIntent(dm, ongoing, pref?.video); - const widget = CallEmbed.getWidget(mx, room, intent, themeKind); + const widget = CallEmbed.getWidget(mx, room, intent, themeKind, callOptions); const controlState = pref && new CallControlState(pref.microphone, pref.video, pref.sound); const embed = new CallEmbed(mx, room, widget, container, controlState); @@ -66,6 +69,7 @@ export const useCallStart = (dm = false) => { const theme = useTheme(); const setCallEmbed = useSetAtom(callEmbedAtom); const callEmbedRef = useCallEmbedRef(); + const clientConfig = useClientConfig(); const startCall = useCallback( (room: Room, pref?: CallPreferences) => { @@ -82,7 +86,15 @@ export const useCallStart = (dm = false) => { try { debugLog.info('call', 'Starting call', { roomId: room.roomId, dm }); Sentry.metrics.count('sable.call.start.attempt', 1, { attributes: { dm: String(dm) } }); - const callEmbed = createCallEmbed(mx, room, dm, theme.kind, container, pref); + const callEmbed = createCallEmbed( + mx, + room, + dm, + theme.kind, + container, + pref, + clientConfig.callOptions + ); setCallEmbed(callEmbed); } catch (err) { debugLog.error('call', 'Call embed creation failed', { @@ -95,7 +107,7 @@ export const useCallStart = (dm = false) => { throw err; } }, - [mx, dm, theme, setCallEmbed, callEmbedRef] + [mx, dm, theme, setCallEmbed, callEmbedRef, clientConfig.callOptions] ); return startCall; diff --git a/src/app/hooks/useClientConfig.ts b/src/app/hooks/useClientConfig.ts index 87685337d..0f77639d7 100644 --- a/src/app/hooks/useClientConfig.ts +++ b/src/app/hooks/useClientConfig.ts @@ -5,11 +5,16 @@ export type HashRouterConfig = { basename?: string; }; +export type CallOptionsConfig = { + autoLeaveWhenOthersLeft?: boolean; +}; + export type ClientConfig = { defaultHomeserver?: number; homeserverList?: string[]; allowCustomHomeservers?: boolean; elementCallUrl?: string; + callOptions?: CallOptionsConfig; disableAccountSwitcher?: boolean; hideUsernamePasswordFields?: boolean; diff --git a/src/app/plugins/call/CallEmbed.ts b/src/app/plugins/call/CallEmbed.ts index 1a12211d4..eb5e7fee0 100644 --- a/src/app/plugins/call/CallEmbed.ts +++ b/src/app/plugins/call/CallEmbed.ts @@ -31,6 +31,10 @@ import { createDebugLogger } from '../../utils/debugLogger'; const debugLog = createDebugLogger('CallEmbed'); +export type CallOptions = { + autoLeaveWhenOthersLeft?: boolean; +}; + export class CallEmbed { private mx: MatrixClient; @@ -68,7 +72,8 @@ export class CallEmbed { mx: MatrixClient, room: Room, intent: ElementCallIntent, - themeKind: ElementCallThemeKind + themeKind: ElementCallThemeKind, + callOptions?: CallOptions ): Widget { const userId = mx.getSafeUserId(); const deviceId = mx.getDeviceId() ?? ''; @@ -92,6 +97,10 @@ export class CallEmbed { theme: themeKind, }); + if (callOptions?.autoLeaveWhenOthersLeft !== undefined) { + params.set('autoLeave', callOptions.autoLeaveWhenOthersLeft.toString()); + } + const widgetUrl = new URL( `${trimTrailingSlash(import.meta.env.BASE_URL)}/public/element-call/index.html`, window.location.origin