Skip to content

Commit e5ca795

Browse files
authored
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 <[email protected]> * Add test for dialpad Signed-off-by: Michael Telatynski <[email protected]> --------- Signed-off-by: Michael Telatynski <[email protected]>
1 parent 13913ba commit e5ca795

File tree

8 files changed

+66
-30
lines changed

8 files changed

+66
-30
lines changed

playwright/e2e/voip/pstn.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
Copyright 2025 New Vector Ltd.
3+
4+
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { test, expect } from "../../element-web-test";
9+
10+
test.describe("PSTN", () => {
11+
test.beforeEach(async ({ page }) => {
12+
// Mock the third party protocols endpoint to look like the HS has PSTN support
13+
await page.route("**/_matrix/client/v3/thirdparty/protocols", async (route) => {
14+
await route.fulfill({
15+
status: 200,
16+
json: {
17+
"im.vector.protocol.pstn": {},
18+
},
19+
});
20+
});
21+
});
22+
23+
test("should render dialpad as expected", { tag: "@screenshot" }, async ({ page, user, toasts }) => {
24+
await toasts.rejectToast("Notifications");
25+
await toasts.assertNoToasts();
26+
27+
await expect(page.locator(".mx_LeftPanel_filterContainer")).toMatchScreenshot("dialpad-trigger.png");
28+
await page.getByLabel("Open dial pad").click();
29+
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("dialpad.png");
30+
});
31+
});
Loading
Loading

src/LegacyCallHandler.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
1010
*/
1111

1212
import React from "react";
13-
import { MatrixError, RuleId, TweakName, SyncState } from "matrix-js-sdk/src/matrix";
13+
import { MatrixError, RuleId, TweakName, SyncState, TypedEventEmitter } from "matrix-js-sdk/src/matrix";
1414
import {
1515
CallError,
1616
CallErrorCode,
@@ -22,7 +22,6 @@ import {
2222
MatrixCall,
2323
} from "matrix-js-sdk/src/webrtc/call";
2424
import { logger } from "matrix-js-sdk/src/logger";
25-
import EventEmitter from "events";
2625
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
2726
import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler";
2827

@@ -137,14 +136,23 @@ export enum LegacyCallHandlerEvent {
137136
CallChangeRoom = "call_change_room",
138137
SilencedCallsChanged = "silenced_calls_changed",
139138
CallState = "call_state",
139+
ProtocolSupport = "protocol_support",
140140
}
141141

142+
type EventEmitterMap = {
143+
[LegacyCallHandlerEvent.CallsChanged]: (calls: Map<string, MatrixCall>) => void;
144+
[LegacyCallHandlerEvent.CallChangeRoom]: (call: MatrixCall) => void;
145+
[LegacyCallHandlerEvent.SilencedCallsChanged]: (calls: Set<string>) => void;
146+
[LegacyCallHandlerEvent.CallState]: (mappedRoomId: string | null, status: CallState) => void;
147+
[LegacyCallHandlerEvent.ProtocolSupport]: () => void;
148+
};
149+
142150
/**
143151
* LegacyCallHandler manages all currently active calls. It should be used for
144152
* placing, answering, rejecting and hanging up calls. It also handles ringing,
145153
* PSTN support and other things.
146154
*/
147-
export default class LegacyCallHandler extends EventEmitter {
155+
export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandlerEvent, EventEmitterMap> {
148156
private calls = new Map<string, MatrixCall>(); // roomId -> call
149157
// Calls started as an attended transfer, ie. with the intention of transferring another
150158
// call with a different party to this one.
@@ -271,15 +279,13 @@ export default class LegacyCallHandler extends EventEmitter {
271279
this.supportsPstnProtocol = null;
272280
}
273281

274-
dis.dispatch({ action: Action.PstnSupportUpdated });
275-
276282
if (protocols[PROTOCOL_SIP_NATIVE] !== undefined && protocols[PROTOCOL_SIP_VIRTUAL] !== undefined) {
277283
this.supportsSipNativeVirtual = Boolean(
278284
protocols[PROTOCOL_SIP_NATIVE] && protocols[PROTOCOL_SIP_VIRTUAL],
279285
);
280286
}
281287

282-
dis.dispatch({ action: Action.VirtualRoomSupportUpdated });
288+
this.emit(LegacyCallHandlerEvent.ProtocolSupport);
283289
} catch (e) {
284290
if (maxTries === 1) {
285291
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 {
296302
return !!SdkConfig.getObject("voip")?.get("obey_asserted_identity");
297303
}
298304

299-
public getSupportsPstnProtocol(): boolean | null {
300-
return this.supportsPstnProtocol;
305+
public getSupportsPstnProtocol(): boolean {
306+
return this.supportsPstnProtocol ?? false;
301307
}
302308

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

570576
this.setCallState(call, newState);
577+
// XXX: this is used by the IPC into Electron to keep device awake
571578
dis.dispatch({
572579
action: "call_state",
573580
room_id: mappedRoomId,

src/components/structures/LeftPanel.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import classNames from "classnames";
1313
import dis from "../../dispatcher/dispatcher";
1414
import { _t } from "../../languageHandler";
1515
import RoomList from "../views/rooms/RoomList";
16-
import LegacyCallHandler from "../../LegacyCallHandler";
16+
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
1717
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist";
1818
import { Action } from "../../dispatcher/actions";
1919
import RoomSearch from "./RoomSearch";
@@ -51,6 +51,7 @@ enum BreadcrumbsMode {
5151
interface IState {
5252
showBreadcrumbs: BreadcrumbsMode;
5353
activeSpace: SpaceKey;
54+
supportsPstnProtocol: boolean;
5455
}
5556

5657
export default class LeftPanel extends React.Component<IProps, IState> {
@@ -65,6 +66,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
6566
this.state = {
6667
activeSpace: SpaceStore.instance.activeSpace,
6768
showBreadcrumbs: LeftPanel.breadcrumbsMode,
69+
supportsPstnProtocol: LegacyCallHandler.instance.getSupportsPstnProtocol(),
6870
};
6971
}
7072

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

8083
if (this.listContainerRef.current) {
8184
UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current);
@@ -90,6 +93,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
9093
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
9194
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
9295
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
96+
LegacyCallHandler.instance.off(LegacyCallHandlerEvent.ProtocolSupport, this.updateProtocolSupport);
9397
UIStore.instance.stopTrackingElementDimensions("ListContainer");
9498
UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders);
9599
this.listContainerRef.current?.removeEventListener("scroll", this.onScroll);
@@ -101,6 +105,10 @@ export default class LeftPanel extends React.Component<IProps, IState> {
101105
}
102106
}
103107

108+
private updateProtocolSupport = (): void => {
109+
this.setState({ supportsPstnProtocol: LegacyCallHandler.instance.getSupportsPstnProtocol() });
110+
};
111+
104112
private updateActiveSpace = (activeSpace: SpaceKey): void => {
105113
this.setState({ activeSpace });
106114
};
@@ -330,9 +338,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {
330338
private renderSearchDialExplore(): React.ReactNode {
331339
let dialPadButton: JSX.Element | undefined;
332340

333-
// If we have dialer support, show a button to bring up the dial pad
334-
// to start a new call
335-
if (LegacyCallHandler.instance.getSupportsPstnProtocol()) {
341+
// If we have dialer support, show a button to bring up the dial pad to start a new call
342+
if (this.state.supportsPstnProtocol) {
336343
dialPadButton = (
337344
<AccessibleButton
338345
className={classNames("mx_LeftPanel_dialPadButton", {})}

src/components/structures/RoomView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
10821082
}
10831083
};
10841084

1085-
private onCallState = (roomId: string): void => {
1085+
private onCallState = (roomId: string | null): void => {
10861086
// don't filter out payloads for room IDs other than props.room because
10871087
// we may be interested in the conf 1:1 room
10881088

src/components/views/rooms/RoomList.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
66
Please see LICENSE files in the repository root for full details.
77
*/
88

9-
import { EventType, RoomType, Room } from "matrix-js-sdk/src/matrix";
9+
import { EventType, Room, RoomType } from "matrix-js-sdk/src/matrix";
1010
import React, { ComponentType, createRef, ReactComponentElement, SyntheticEvent } from "react";
1111

1212
import { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
@@ -56,6 +56,7 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
5656
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
5757
import AccessibleButton from "../elements/AccessibleButton";
5858
import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation";
59+
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler.tsx";
5960

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

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

456+
private updateProtocolSupport = (): void => {
457+
this.updateLists();
458+
};
459+
453460
private onRoomViewStoreUpdate = (): void => {
454461
this.setState({
455462
currentRoomId: SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined,
@@ -471,8 +478,6 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
471478
metricsViaKeyboard: true,
472479
});
473480
}
474-
} else if (payload.action === Action.PstnSupportUpdated) {
475-
this.updateLists();
476481
}
477482
};
478483

src/dispatcher/actions.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -135,20 +135,6 @@ export enum Action {
135135
*/
136136
OpenDialPad = "open_dial_pad",
137137

138-
/**
139-
* Fired when CallHandler has checked for PSTN protocol support
140-
* payload: none
141-
* XXX: Is an action the right thing for this?
142-
*/
143-
PstnSupportUpdated = "pstn_support_updated",
144-
145-
/**
146-
* Similar to PstnSupportUpdated, fired when CallHandler has checked for virtual room support
147-
* payload: none
148-
* XXX: Ditto
149-
*/
150-
VirtualRoomSupportUpdated = "virtual_room_support_updated",
151-
152138
/**
153139
* Fired when an upload has started. Should be used with UploadStartedPayload.
154140
*/

0 commit comments

Comments
 (0)