mirror of
https://github.com/vector-im/element-web.git
synced 2025-11-15 07:31:08 +01:00
Refactor LegacyCallHandler event emitter to use TypedEventEmitter (#29008)
* Switch LegacyCallHandler over to TypedEventEmitter and use emits to notify consumers of protocol support updates Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add test for dialpad Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
13913ba8b2
commit
e5ca7954c8
31
playwright/e2e/voip/pstn.spec.ts
Normal file
31
playwright/e2e/voip/pstn.spec.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from "../../element-web-test";
|
||||||
|
|
||||||
|
test.describe("PSTN", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Mock the third party protocols endpoint to look like the HS has PSTN support
|
||||||
|
await page.route("**/_matrix/client/v3/thirdparty/protocols", async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
json: {
|
||||||
|
"im.vector.protocol.pstn": {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should render dialpad as expected", { tag: "@screenshot" }, async ({ page, user, toasts }) => {
|
||||||
|
await toasts.rejectToast("Notifications");
|
||||||
|
await toasts.assertNoToasts();
|
||||||
|
|
||||||
|
await expect(page.locator(".mx_LeftPanel_filterContainer")).toMatchScreenshot("dialpad-trigger.png");
|
||||||
|
await page.getByLabel("Open dial pad").click();
|
||||||
|
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("dialpad.png");
|
||||||
|
});
|
||||||
|
});
|
||||||
BIN
playwright/snapshots/voip/pstn.spec.ts/dialpad-linux.png
Normal file
BIN
playwright/snapshots/voip/pstn.spec.ts/dialpad-linux.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
playwright/snapshots/voip/pstn.spec.ts/dialpad-trigger-linux.png
Normal file
BIN
playwright/snapshots/voip/pstn.spec.ts/dialpad-trigger-linux.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { MatrixError, RuleId, TweakName, SyncState } from "matrix-js-sdk/src/matrix";
|
import { MatrixError, RuleId, TweakName, SyncState, TypedEventEmitter } from "matrix-js-sdk/src/matrix";
|
||||||
import {
|
import {
|
||||||
CallError,
|
CallError,
|
||||||
CallErrorCode,
|
CallErrorCode,
|
||||||
@ -22,7 +22,6 @@ import {
|
|||||||
MatrixCall,
|
MatrixCall,
|
||||||
} from "matrix-js-sdk/src/webrtc/call";
|
} from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import EventEmitter from "events";
|
|
||||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||||
import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler";
|
import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler";
|
||||||
|
|
||||||
@ -137,14 +136,23 @@ export enum LegacyCallHandlerEvent {
|
|||||||
CallChangeRoom = "call_change_room",
|
CallChangeRoom = "call_change_room",
|
||||||
SilencedCallsChanged = "silenced_calls_changed",
|
SilencedCallsChanged = "silenced_calls_changed",
|
||||||
CallState = "call_state",
|
CallState = "call_state",
|
||||||
|
ProtocolSupport = "protocol_support",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventEmitterMap = {
|
||||||
|
[LegacyCallHandlerEvent.CallsChanged]: (calls: Map<string, MatrixCall>) => void;
|
||||||
|
[LegacyCallHandlerEvent.CallChangeRoom]: (call: MatrixCall) => void;
|
||||||
|
[LegacyCallHandlerEvent.SilencedCallsChanged]: (calls: Set<string>) => void;
|
||||||
|
[LegacyCallHandlerEvent.CallState]: (mappedRoomId: string | null, status: CallState) => void;
|
||||||
|
[LegacyCallHandlerEvent.ProtocolSupport]: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LegacyCallHandler manages all currently active calls. It should be used for
|
* LegacyCallHandler manages all currently active calls. It should be used for
|
||||||
* placing, answering, rejecting and hanging up calls. It also handles ringing,
|
* placing, answering, rejecting and hanging up calls. It also handles ringing,
|
||||||
* PSTN support and other things.
|
* PSTN support and other things.
|
||||||
*/
|
*/
|
||||||
export default class LegacyCallHandler extends EventEmitter {
|
export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandlerEvent, EventEmitterMap> {
|
||||||
private calls = new Map<string, MatrixCall>(); // roomId -> call
|
private calls = new Map<string, MatrixCall>(); // roomId -> call
|
||||||
// Calls started as an attended transfer, ie. with the intention of transferring another
|
// Calls started as an attended transfer, ie. with the intention of transferring another
|
||||||
// call with a different party to this one.
|
// call with a different party to this one.
|
||||||
@ -271,15 +279,13 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||||||
this.supportsPstnProtocol = null;
|
this.supportsPstnProtocol = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
dis.dispatch({ action: Action.PstnSupportUpdated });
|
|
||||||
|
|
||||||
if (protocols[PROTOCOL_SIP_NATIVE] !== undefined && protocols[PROTOCOL_SIP_VIRTUAL] !== undefined) {
|
if (protocols[PROTOCOL_SIP_NATIVE] !== undefined && protocols[PROTOCOL_SIP_VIRTUAL] !== undefined) {
|
||||||
this.supportsSipNativeVirtual = Boolean(
|
this.supportsSipNativeVirtual = Boolean(
|
||||||
protocols[PROTOCOL_SIP_NATIVE] && protocols[PROTOCOL_SIP_VIRTUAL],
|
protocols[PROTOCOL_SIP_NATIVE] && protocols[PROTOCOL_SIP_VIRTUAL],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dis.dispatch({ action: Action.VirtualRoomSupportUpdated });
|
this.emit(LegacyCallHandlerEvent.ProtocolSupport);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (maxTries === 1) {
|
if (maxTries === 1) {
|
||||||
logger.log("Failed to check for protocol support and no retries remain: assuming no support", e);
|
logger.log("Failed to check for protocol support and no retries remain: assuming no support", e);
|
||||||
@ -296,8 +302,8 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||||||
return !!SdkConfig.getObject("voip")?.get("obey_asserted_identity");
|
return !!SdkConfig.getObject("voip")?.get("obey_asserted_identity");
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSupportsPstnProtocol(): boolean | null {
|
public getSupportsPstnProtocol(): boolean {
|
||||||
return this.supportsPstnProtocol;
|
return this.supportsPstnProtocol ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSupportsVirtualRooms(): boolean | null {
|
public getSupportsVirtualRooms(): boolean | null {
|
||||||
@ -568,6 +574,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||||||
if (!mappedRoomId || !this.matchesCallForThisRoom(call)) return;
|
if (!mappedRoomId || !this.matchesCallForThisRoom(call)) return;
|
||||||
|
|
||||||
this.setCallState(call, newState);
|
this.setCallState(call, newState);
|
||||||
|
// XXX: this is used by the IPC into Electron to keep device awake
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "call_state",
|
action: "call_state",
|
||||||
room_id: mappedRoomId,
|
room_id: mappedRoomId,
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import classNames from "classnames";
|
|||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import RoomList from "../views/rooms/RoomList";
|
import RoomList from "../views/rooms/RoomList";
|
||||||
import LegacyCallHandler from "../../LegacyCallHandler";
|
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||||
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist";
|
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import RoomSearch from "./RoomSearch";
|
import RoomSearch from "./RoomSearch";
|
||||||
@ -51,6 +51,7 @@ enum BreadcrumbsMode {
|
|||||||
interface IState {
|
interface IState {
|
||||||
showBreadcrumbs: BreadcrumbsMode;
|
showBreadcrumbs: BreadcrumbsMode;
|
||||||
activeSpace: SpaceKey;
|
activeSpace: SpaceKey;
|
||||||
|
supportsPstnProtocol: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class LeftPanel extends React.Component<IProps, IState> {
|
export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
@ -65,6 +66,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||||||
this.state = {
|
this.state = {
|
||||||
activeSpace: SpaceStore.instance.activeSpace,
|
activeSpace: SpaceStore.instance.activeSpace,
|
||||||
showBreadcrumbs: LeftPanel.breadcrumbsMode,
|
showBreadcrumbs: LeftPanel.breadcrumbsMode,
|
||||||
|
supportsPstnProtocol: LegacyCallHandler.instance.getSupportsPstnProtocol(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +78,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||||||
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
||||||
|
LegacyCallHandler.instance.on(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport);
|
||||||
|
|
||||||
if (this.listContainerRef.current) {
|
if (this.listContainerRef.current) {
|
||||||
UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current);
|
UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current);
|
||||||
@ -90,6 +93,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||||||
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
||||||
|
LegacyCallHandler.instance.off(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport);
|
||||||
UIStore.instance.stopTrackingElementDimensions("ListContainer");
|
UIStore.instance.stopTrackingElementDimensions("ListContainer");
|
||||||
UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders);
|
UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders);
|
||||||
this.listContainerRef.current?.removeEventListener("scroll", this.onScroll);
|
this.listContainerRef.current?.removeEventListener("scroll", this.onScroll);
|
||||||
@ -101,6 +105,10 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateProtocolSupport = (): void => {
|
||||||
|
this.setState({ supportsPstnProtocol: LegacyCallHandler.instance.getSupportsPstnProtocol() });
|
||||||
|
};
|
||||||
|
|
||||||
private updateActiveSpace = (activeSpace: SpaceKey): void => {
|
private updateActiveSpace = (activeSpace: SpaceKey): void => {
|
||||||
this.setState({ activeSpace });
|
this.setState({ activeSpace });
|
||||||
};
|
};
|
||||||
@ -330,9 +338,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||||||
private renderSearchDialExplore(): React.ReactNode {
|
private renderSearchDialExplore(): React.ReactNode {
|
||||||
let dialPadButton: JSX.Element | undefined;
|
let dialPadButton: JSX.Element | undefined;
|
||||||
|
|
||||||
// If we have dialer support, show a button to bring up the dial pad
|
// If we have dialer support, show a button to bring up the dial pad to start a new call
|
||||||
// to start a new call
|
if (this.state.supportsPstnProtocol) {
|
||||||
if (LegacyCallHandler.instance.getSupportsPstnProtocol()) {
|
|
||||||
dialPadButton = (
|
dialPadButton = (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className={classNames("mx_LeftPanel_dialPadButton", {})}
|
className={classNames("mx_LeftPanel_dialPadButton", {})}
|
||||||
|
|||||||
@ -1082,7 +1082,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCallState = (roomId: string): void => {
|
private onCallState = (roomId: string | null): void => {
|
||||||
// don't filter out payloads for room IDs other than props.room because
|
// don't filter out payloads for room IDs other than props.room because
|
||||||
// we may be interested in the conf 1:1 room
|
// we may be interested in the conf 1:1 room
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventType, RoomType, Room } from "matrix-js-sdk/src/matrix";
|
import { EventType, Room, RoomType } from "matrix-js-sdk/src/matrix";
|
||||||
import React, { ComponentType, createRef, ReactComponentElement, SyntheticEvent } from "react";
|
import React, { ComponentType, createRef, ReactComponentElement, SyntheticEvent } from "react";
|
||||||
|
|
||||||
import { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
import { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
||||||
@ -56,6 +56,7 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
|||||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation";
|
import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation";
|
||||||
|
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler.tsx";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void;
|
onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void;
|
||||||
@ -440,6 +441,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||||||
SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||||
SpaceStore.instance.on(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms);
|
SpaceStore.instance.on(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms);
|
||||||
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists);
|
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists);
|
||||||
|
LegacyCallHandler.instance.on(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport);
|
||||||
this.updateLists(); // trigger the first update
|
this.updateLists(); // trigger the first update
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,8 +450,13 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||||||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
|
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
|
||||||
defaultDispatcher.unregister(this.dispatcherRef);
|
defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||||
|
LegacyCallHandler.instance.off(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateProtocolSupport = (): void => {
|
||||||
|
this.updateLists();
|
||||||
|
};
|
||||||
|
|
||||||
private onRoomViewStoreUpdate = (): void => {
|
private onRoomViewStoreUpdate = (): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentRoomId: SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined,
|
currentRoomId: SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined,
|
||||||
@ -471,8 +478,6 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||||||
metricsViaKeyboard: true,
|
metricsViaKeyboard: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (payload.action === Action.PstnSupportUpdated) {
|
|
||||||
this.updateLists();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -135,20 +135,6 @@ export enum Action {
|
|||||||
*/
|
*/
|
||||||
OpenDialPad = "open_dial_pad",
|
OpenDialPad = "open_dial_pad",
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when CallHandler has checked for PSTN protocol support
|
|
||||||
* payload: none
|
|
||||||
* XXX: Is an action the right thing for this?
|
|
||||||
*/
|
|
||||||
PstnSupportUpdated = "pstn_support_updated",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to PstnSupportUpdated, fired when CallHandler has checked for virtual room support
|
|
||||||
* payload: none
|
|
||||||
* XXX: Ditto
|
|
||||||
*/
|
|
||||||
VirtualRoomSupportUpdated = "virtual_room_support_updated",
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when an upload has started. Should be used with UploadStartedPayload.
|
* Fired when an upload has started. Should be used with UploadStartedPayload.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user