mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-24 00:38:31 +08:00
1897210a60
* Initial support for Hand Raise feature Signed-off-by: Milton Moura <miltonmoura@gmail.com> * Refactored to use reaction and redaction events Signed-off-by: Milton Moura <miltonmoura@gmail.com> * Replacing button svg with raised hand emoji Signed-off-by: Milton Moura <miltonmoura@gmail.com> * SpotlightTile should not duplicate the raised hand Signed-off-by: Milton Moura <miltonmoura@gmail.com> * Update src/room/useRaisedHands.tsx Element Call recently changed to AGPL-3.0 * Use relations to load existing reactions when joining the call Signed-off-by: Milton Moura <miltonmoura@gmail.com> * Links to sha commit of matrix-js-sdk that exposes the call membership event id and refactors some async code Signed-off-by: Milton Moura <miltonmoura@gmail.com> * Removing RaiseHand.svg * Check for reaction & redaction capabilities in widget mode Signed-off-by: Milton Moura <miltonmoura@gmail.com> * Fix failing GridTile test Signed-off-by: Milton Moura <miltonmoura@gmail.com> * Center align hand raise. * Add support for displaying the duration of a raised hand. * Add a sound for when a hand is raised. * Refactor raised hand indicator and add tests. * lint * Refactor into own files. * Redact the right thing. * Tidy up useEffect * Lint tests * Remove extra layer * Add better sound. (woosh) * Add a small mode for spotlight * Fix timestamp calculation on relaod. * Fix call border resizing video * lint * Fix and update tests * Allow timer to be configurable. * Add preferences tab for choosing to enable timer. * Drop border from raised hand icon * Handle cases when a new member event happens. * Prevent infinite loop * Major refactor to support various state problems. * Tidy up and finish test rewrites * Add some explanation comments. * Even more comments. * Use proper duration formatter * Remove rerender * Fix redactions not working because they pick up events in transit. * More tidying * Use deferred value * linting * Add tests for cases where we got a reaction from someone else. * Be even less brittle. * Transpose border to GridTile. * lint --------- Signed-off-by: Milton Moura <miltonmoura@gmail.com> Co-authored-by: fkwp <fkwp@users.noreply.github.com> Co-authored-by: Half-Shot <will@half-shot.uk> Co-authored-by: Will Hunt <github@half-shot.uk>
182 lines
6.2 KiB
TypeScript
182 lines
6.2 KiB
TypeScript
/*
|
|
Copyright 2022-2024 New Vector Ltd.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
|
import { createRoomWidgetClient } from "matrix-js-sdk/src/matrix";
|
|
import { WidgetApi, MatrixCapabilities } from "matrix-widget-api";
|
|
|
|
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
|
import { LazyEventEmitter } from "./LazyEventEmitter";
|
|
import { getUrlParams } from "./UrlParams";
|
|
import { Config } from "./config/Config";
|
|
|
|
// Subset of the actions in matrix-react-sdk
|
|
export enum ElementWidgetActions {
|
|
JoinCall = "io.element.join",
|
|
HangupCall = "im.vector.hangup",
|
|
TileLayout = "io.element.tile_layout",
|
|
SpotlightLayout = "io.element.spotlight_layout",
|
|
// This can be sent as from or to widget
|
|
// fromWidget: updates the client about the current device mute state
|
|
// toWidget: the client requests a specific device mute configuration
|
|
// The reply will always be the resulting configuration
|
|
// It is possible to sent an empty configuration to retrieve the current values or
|
|
// just one of the fields to update that particular value
|
|
// An undefined field means that EC will keep the mute state as is.
|
|
// -> this will allow the client to only get the current state
|
|
//
|
|
// The data of the widget action request and the response are:
|
|
// {
|
|
// audio_enabled?: boolean,
|
|
// video_enabled?: boolean
|
|
// }
|
|
DeviceMute = "io.element.device_mute",
|
|
}
|
|
|
|
export interface JoinCallData {
|
|
audioInput: string | null;
|
|
videoInput: string | null;
|
|
}
|
|
|
|
export interface WidgetHelpers {
|
|
api: WidgetApi;
|
|
lazyActions: LazyEventEmitter;
|
|
client: Promise<MatrixClient>;
|
|
}
|
|
|
|
/**
|
|
* A point of access to the widget API, if the app is running as a widget. This
|
|
* is declared and initialized on the top level because the widget messaging
|
|
* needs to be set up ASAP on load to ensure it doesn't miss any requests.
|
|
*/
|
|
export const widget = ((): WidgetHelpers | null => {
|
|
try {
|
|
const { widgetId, parentUrl } = getUrlParams();
|
|
|
|
if (widgetId && parentUrl) {
|
|
const parentOrigin = new URL(parentUrl).origin;
|
|
logger.info("Widget API is available");
|
|
const api = new WidgetApi(widgetId, parentOrigin);
|
|
api.requestCapability(MatrixCapabilities.AlwaysOnScreen);
|
|
|
|
// Set up the lazy action emitter, but only for select actions that we
|
|
// intend for the app to handle
|
|
const lazyActions = new LazyEventEmitter();
|
|
[
|
|
ElementWidgetActions.JoinCall,
|
|
ElementWidgetActions.HangupCall,
|
|
ElementWidgetActions.TileLayout,
|
|
ElementWidgetActions.SpotlightLayout,
|
|
ElementWidgetActions.DeviceMute,
|
|
].forEach((action) => {
|
|
api.on(`action:${action}`, (ev: CustomEvent<IWidgetApiRequest>) => {
|
|
ev.preventDefault();
|
|
lazyActions.emit(action, ev);
|
|
});
|
|
});
|
|
|
|
// Now, initialize the matryoshka MatrixClient (so named because it routes
|
|
// all requests through the host client via the widget API)
|
|
// We need to do this now rather than later because it has capabilities to
|
|
// request, and is responsible for starting the transport (should it be?)
|
|
|
|
const {
|
|
roomId,
|
|
userId,
|
|
deviceId,
|
|
baseUrl,
|
|
e2eEnabled,
|
|
allowIceFallback,
|
|
} = getUrlParams();
|
|
if (!roomId) throw new Error("Room ID must be supplied");
|
|
if (!userId) throw new Error("User ID must be supplied");
|
|
if (!deviceId) throw new Error("Device ID must be supplied");
|
|
if (!baseUrl) throw new Error("Base URL must be supplied");
|
|
|
|
// These are all the event types the app uses
|
|
const sendRecvEvent = [
|
|
"org.matrix.rageshake_request",
|
|
EventType.CallEncryptionKeysPrefix,
|
|
EventType.Reaction,
|
|
EventType.RoomRedaction,
|
|
];
|
|
|
|
const sendState = [
|
|
userId, // Legacy call membership events
|
|
`_${userId}_${deviceId}`, // Session membership events
|
|
`${userId}_${deviceId}`, // The above with no leading underscore, for room versions whose auth rules allow it
|
|
].map((stateKey) => ({
|
|
eventType: EventType.GroupCallMemberPrefix,
|
|
stateKey,
|
|
}));
|
|
const receiveState = [
|
|
{ eventType: EventType.RoomCreate },
|
|
{ eventType: EventType.RoomMember },
|
|
{ eventType: EventType.RoomEncryption },
|
|
{ eventType: EventType.GroupCallMemberPrefix },
|
|
];
|
|
const sendRecvToDevice = [
|
|
EventType.CallInvite,
|
|
EventType.CallCandidates,
|
|
EventType.CallAnswer,
|
|
EventType.CallHangup,
|
|
EventType.CallReject,
|
|
EventType.CallSelectAnswer,
|
|
EventType.CallNegotiate,
|
|
EventType.CallSDPStreamMetadataChanged,
|
|
EventType.CallSDPStreamMetadataChangedPrefix,
|
|
EventType.CallReplaces,
|
|
];
|
|
|
|
const client = createRoomWidgetClient(
|
|
api,
|
|
{
|
|
sendEvent: sendRecvEvent,
|
|
receiveEvent: sendRecvEvent,
|
|
sendState,
|
|
receiveState,
|
|
sendToDevice: sendRecvToDevice,
|
|
receiveToDevice: sendRecvToDevice,
|
|
turnServers: false,
|
|
sendDelayedEvents: true,
|
|
updateDelayedEvents: true,
|
|
},
|
|
roomId,
|
|
{
|
|
baseUrl,
|
|
userId,
|
|
deviceId,
|
|
timelineSupport: true,
|
|
useE2eForGroupCall: e2eEnabled,
|
|
fallbackICEServerAllowed: allowIceFallback,
|
|
},
|
|
// ContentLoaded event will be sent as soon as the theme is set (see useTheme.ts)
|
|
false,
|
|
);
|
|
|
|
const clientPromise = async (): Promise<MatrixClient> => {
|
|
// Wait for the config file to be ready (we load very early on so it might not
|
|
// be otherwise)
|
|
await Config.init();
|
|
await client.startClient({ clientWellKnownPollPeriod: 60 * 10 });
|
|
return client;
|
|
};
|
|
|
|
return { api, lazyActions, client: clientPromise() };
|
|
} else {
|
|
if (import.meta.env.MODE !== "test")
|
|
logger.info("No widget API available");
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
logger.warn("Continuing without the widget API", e);
|
|
return null;
|
|
}
|
|
})();
|