Skip to content

Commit

Permalink
Refactor LegacyCallHandler event emitter to use TypedEventEmitter (#2…
Browse files Browse the repository at this point in the history
…9008)

* 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>
  • Loading branch information
t3chguy authored Jan 16, 2025
1 parent 13913ba commit e5ca795
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 30 deletions.
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

0 comments on commit e5ca795

Please sign in to comment.