Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor LegacyCallHandler event emitter to use TypedEventEmitter #29008

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions playwright/e2e/voip/pstn.spec.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 15 additions & 8 deletions src/LegacyCallHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
*/

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 {
CallError,
CallErrorCode,
Expand All @@ -22,7 +22,6 @@ import {
MatrixCall,
} from "matrix-js-sdk/src/webrtc/call";
import { logger } from "matrix-js-sdk/src/logger";
import EventEmitter from "events";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler";

Expand Down Expand Up @@ -137,14 +136,23 @@ export enum LegacyCallHandlerEvent {
CallChangeRoom = "call_change_room",
SilencedCallsChanged = "silenced_calls_changed",
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
* placing, answering, rejecting and hanging up calls. It also handles ringing,
* 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
// Calls started as an attended transfer, ie. with the intention of transferring another
// call with a different party to this one.
Expand Down Expand Up @@ -271,15 +279,13 @@ export default class LegacyCallHandler extends EventEmitter {
this.supportsPstnProtocol = null;
}

dis.dispatch({ action: Action.PstnSupportUpdated });

if (protocols[PROTOCOL_SIP_NATIVE] !== undefined && protocols[PROTOCOL_SIP_VIRTUAL] !== undefined) {
this.supportsSipNativeVirtual = Boolean(
protocols[PROTOCOL_SIP_NATIVE] && protocols[PROTOCOL_SIP_VIRTUAL],
);
}

dis.dispatch({ action: Action.VirtualRoomSupportUpdated });
this.emit(LegacyCallHandlerEvent.ProtocolSupport);
} catch (e) {
if (maxTries === 1) {
logger.log("Failed to check for protocol support and no retries remain: assuming no support", e);
Expand All @@ -296,8 +302,8 @@ export default class LegacyCallHandler extends EventEmitter {
return !!SdkConfig.getObject("voip")?.get("obey_asserted_identity");
}

public getSupportsPstnProtocol(): boolean | null {
return this.supportsPstnProtocol;
public getSupportsPstnProtocol(): boolean {
return this.supportsPstnProtocol ?? false;
}

public getSupportsVirtualRooms(): boolean | null {
Expand Down Expand Up @@ -568,6 +574,7 @@ export default class LegacyCallHandler extends EventEmitter {
if (!mappedRoomId || !this.matchesCallForThisRoom(call)) return;

this.setCallState(call, newState);
// XXX: this is used by the IPC into Electron to keep device awake
dis.dispatch({
action: "call_state",
room_id: mappedRoomId,
Expand Down
15 changes: 11 additions & 4 deletions src/components/structures/LeftPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import classNames from "classnames";
import dis from "../../dispatcher/dispatcher";
import { _t } from "../../languageHandler";
import RoomList from "../views/rooms/RoomList";
import LegacyCallHandler from "../../LegacyCallHandler";
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist";
import { Action } from "../../dispatcher/actions";
import RoomSearch from "./RoomSearch";
Expand Down Expand Up @@ -51,6 +51,7 @@ enum BreadcrumbsMode {
interface IState {
showBreadcrumbs: BreadcrumbsMode;
activeSpace: SpaceKey;
supportsPstnProtocol: boolean;
}

export default class LeftPanel extends React.Component<IProps, IState> {
Expand All @@ -65,6 +66,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
this.state = {
activeSpace: SpaceStore.instance.activeSpace,
showBreadcrumbs: LeftPanel.breadcrumbsMode,
supportsPstnProtocol: LegacyCallHandler.instance.getSupportsPstnProtocol(),
};
}

Expand All @@ -76,6 +78,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
LegacyCallHandler.instance.on(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport);

if (this.listContainerRef.current) {
UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current);
Expand All @@ -90,6 +93,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
LegacyCallHandler.instance.off(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport);
UIStore.instance.stopTrackingElementDimensions("ListContainer");
UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders);
this.listContainerRef.current?.removeEventListener("scroll", this.onScroll);
Expand All @@ -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 => {
this.setState({ activeSpace });
};
Expand Down Expand Up @@ -330,9 +338,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {
private renderSearchDialExplore(): React.ReactNode {
let dialPadButton: JSX.Element | undefined;

// If we have dialer support, show a button to bring up the dial pad
// to start a new call
if (LegacyCallHandler.instance.getSupportsPstnProtocol()) {
// If we have dialer support, show a button to bring up the dial pad to start a new call
if (this.state.supportsPstnProtocol) {
dialPadButton = (
<AccessibleButton
className={classNames("mx_LeftPanel_dialPadButton", {})}
Expand Down
2 changes: 1 addition & 1 deletion src/components/structures/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
// we may be interested in the conf 1:1 room

Expand Down
11 changes: 8 additions & 3 deletions src/components/views/rooms/RoomList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/

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 { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
Expand Down Expand Up @@ -56,6 +56,7 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import AccessibleButton from "../elements/AccessibleButton";
import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation";
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler.tsx";

interface IProps {
onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void;
Expand Down Expand Up @@ -440,6 +441,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
SpaceStore.instance.on(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms);
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists);
LegacyCallHandler.instance.on(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport);
this.updateLists(); // trigger the first update
}

Expand All @@ -448,8 +450,13 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
defaultDispatcher.unregister(this.dispatcherRef);
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
LegacyCallHandler.instance.off(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport);
}

private updateProtocolSupport = (): void => {
this.updateLists();
};

private onRoomViewStoreUpdate = (): void => {
this.setState({
currentRoomId: SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined,
Expand All @@ -471,8 +478,6 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
metricsViaKeyboard: true,
});
}
} else if (payload.action === Action.PstnSupportUpdated) {
this.updateLists();
}
};

Expand Down
14 changes: 0 additions & 14 deletions src/dispatcher/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,6 @@ export enum Action {
*/
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.
*/
Expand Down
Loading