Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 744eeb5

Browse files
authored
Don't assume that widget IDs are unique (#8052)
* Don't assume that widget IDs are unique Signed-off-by: Robin Townsend <[email protected]> * Don't remove live tiles that don't exist Signed-off-by: Robin Townsend <[email protected]> * Add unit test for AppTile's live tile tracking Signed-off-by: Robin Townsend <[email protected]>
1 parent bc8fdac commit 744eeb5

File tree

13 files changed

+276
-159
lines changed

13 files changed

+276
-159
lines changed

src/CallHandler.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1123,7 +1123,7 @@ export default class CallHandler extends EventEmitter {
11231123

11241124
const jitsiWidgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type));
11251125
jitsiWidgets.forEach(w => {
1126-
const messaging = WidgetMessagingStore.instance.getMessagingForId(w.id);
1126+
const messaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(w));
11271127
if (!messaging) return; // more "should never happen" words
11281128

11291129
messaging.transport.send(ElementWidgetActions.HangupCall, {});

src/components/views/context_menus/WidgetContextMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const WidgetContextMenu: React.FC<IProps> = ({
5757
const cli = useContext(MatrixClientContext);
5858
const { room, roomId } = useContext(RoomContext);
5959

60-
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForId(app.id);
60+
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(app));
6161
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(roomId);
6262

6363
let streamAudioStreamButton;

src/components/views/elements/AppTile.tsx

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -118,24 +118,27 @@ export default class AppTile extends React.Component<IProps, IState> {
118118
showLayoutButtons: true,
119119
};
120120

121-
// We track a count of all "live" `AppTile`s for a given widget ID.
121+
// We track a count of all "live" `AppTile`s for a given widget UID.
122122
// For this purpose, an `AppTile` is considered live from the time it is
123123
// constructed until it is unmounted. This is used to aid logic around when
124124
// to tear down the widget iframe. See `componentWillUnmount` for details.
125-
private static liveTilesById: { [key: string]: number } = {};
125+
private static liveTilesByUid = new Map<string, number>();
126126

127-
public static addLiveTile(widgetId: string): void {
128-
const refs = this.liveTilesById[widgetId] || 0;
129-
this.liveTilesById[widgetId] = refs + 1;
127+
public static addLiveTile(widgetId: string, roomId: string): void {
128+
const uid = WidgetUtils.calcWidgetUid(widgetId, roomId);
129+
const refs = this.liveTilesByUid.get(uid) ?? 0;
130+
this.liveTilesByUid.set(uid, refs + 1);
130131
}
131132

132-
public static removeLiveTile(widgetId: string): void {
133-
const refs = this.liveTilesById[widgetId] || 0;
134-
this.liveTilesById[widgetId] = refs - 1;
133+
public static removeLiveTile(widgetId: string, roomId: string): void {
134+
const uid = WidgetUtils.calcWidgetUid(widgetId, roomId);
135+
const refs = this.liveTilesByUid.get(uid);
136+
if (refs) this.liveTilesByUid.set(uid, refs - 1);
135137
}
136138

137-
public static isLive(widgetId: string): boolean {
138-
const refs = this.liveTilesById[widgetId] || 0;
139+
public static isLive(widgetId: string, roomId: string): boolean {
140+
const uid = WidgetUtils.calcWidgetUid(widgetId, roomId);
141+
const refs = this.liveTilesByUid.get(uid) ?? 0;
139142
return refs > 0;
140143
}
141144

@@ -150,10 +153,10 @@ export default class AppTile extends React.Component<IProps, IState> {
150153
constructor(props: IProps) {
151154
super(props);
152155

153-
AppTile.addLiveTile(this.props.app.id);
156+
AppTile.addLiveTile(this.props.app.id, this.props.app.roomId);
154157

155158
// The key used for PersistedElement
156-
this.persistKey = getPersistKey(this.props.app.id);
159+
this.persistKey = getPersistKey(WidgetUtils.getWidgetUid(this.props.app));
157160
try {
158161
this.sgWidget = new StopGapWidget(this.props);
159162
this.setupSgListeners();
@@ -189,7 +192,9 @@ export default class AppTile extends React.Component<IProps, IState> {
189192
};
190193

191194
private onUserLeftRoom() {
192-
const isActiveWidget = ActiveWidgetStore.instance.getWidgetPersistence(this.props.app.id);
195+
const isActiveWidget = ActiveWidgetStore.instance.getWidgetPersistence(
196+
this.props.app.id, this.props.app.roomId,
197+
);
193198
if (isActiveWidget) {
194199
// We just left the room that the active widget was from.
195200
if (this.props.room && RoomViewStore.getRoomId() !== this.props.room.roomId) {
@@ -200,7 +205,7 @@ export default class AppTile extends React.Component<IProps, IState> {
200205
this.reload();
201206
} else {
202207
// Otherwise just cancel its persistence.
203-
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
208+
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id, this.props.app.roomId);
204209
}
205210
}
206211
}
@@ -241,7 +246,7 @@ export default class AppTile extends React.Component<IProps, IState> {
241246

242247
if (this.state.hasPermissionToLoad && !hasPermissionToLoad) {
243248
// Force the widget to be non-persistent (able to be deleted/forgotten)
244-
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
249+
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id, this.props.app.roomId);
245250
PersistedElement.destroyElement(this.persistKey);
246251
this.sgWidget?.stopMessaging();
247252
}
@@ -291,14 +296,16 @@ export default class AppTile extends React.Component<IProps, IState> {
291296
// container is constructed before the old one unmounts. By counting the
292297
// mounted `AppTile`s for each widget, we know to only tear down the
293298
// widget iframe when the last the `AppTile` unmounts.
294-
AppTile.removeLiveTile(this.props.app.id);
299+
AppTile.removeLiveTile(this.props.app.id, this.props.app.roomId);
295300

296301
// We also support a separate "persistence" mode where a single widget
297302
// can request to be "sticky" and follow you across rooms in a PIP
298303
// container.
299-
const isActiveWidget = ActiveWidgetStore.instance.getWidgetPersistence(this.props.app.id);
304+
const isActiveWidget = ActiveWidgetStore.instance.getWidgetPersistence(
305+
this.props.app.id, this.props.app.roomId,
306+
);
300307

301-
if (!AppTile.isLive(this.props.app.id) && !isActiveWidget) {
308+
if (!AppTile.isLive(this.props.app.id, this.props.app.roomId) && !isActiveWidget) {
302309
this.endWidgetActions();
303310
}
304311

@@ -408,7 +415,7 @@ export default class AppTile extends React.Component<IProps, IState> {
408415

409416
// Delete the widget from the persisted store for good measure.
410417
PersistedElement.destroyElement(this.persistKey);
411-
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
418+
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id, this.props.app.roomId);
412419

413420
this.sgWidget?.stopMessaging({ forceDestroy: true });
414421
}

src/components/views/elements/PersistentApp.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ limitations under the License.
1616
*/
1717

1818
import React, { ContextType } from 'react';
19+
import { Room } from "matrix-js-sdk/src/models/room";
1920

20-
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
2121
import WidgetUtils from '../../../utils/WidgetUtils';
2222
import { replaceableComponent } from "../../../utils/replaceableComponent";
2323
import AppTile from "./AppTile";
@@ -26,39 +26,40 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
2626

2727
interface IProps {
2828
persistentWidgetId: string;
29+
persistentRoomId: string;
2930
pointerEvents?: string;
3031
}
3132

3233
@replaceableComponent("views.elements.PersistentApp")
3334
export default class PersistentApp extends React.Component<IProps> {
3435
public static contextType = MatrixClientContext;
3536
context: ContextType<typeof MatrixClientContext>;
37+
private room: Room;
3638

37-
private get app(): IApp {
38-
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.props.persistentWidgetId);
39-
const persistentWidgetInRoom = this.context.getRoom(persistentWidgetInRoomId);
39+
constructor(props: IProps, context: ContextType<typeof MatrixClientContext>) {
40+
super(props, context);
41+
this.room = context.getRoom(this.props.persistentRoomId);
42+
}
4043

44+
private get app(): IApp {
4145
// get the widget data
42-
const appEvent = WidgetUtils.getRoomWidgets(persistentWidgetInRoom).find((ev) => {
43-
return ev.getStateKey() === ActiveWidgetStore.instance.getPersistentWidgetId();
44-
});
46+
const appEvent = WidgetUtils.getRoomWidgets(this.room).find(ev =>
47+
ev.getStateKey() === this.props.persistentWidgetId,
48+
);
4549
return WidgetUtils.makeAppConfig(
4650
appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(),
47-
persistentWidgetInRoomId, appEvent.getId(),
51+
this.room.roomId, appEvent.getId(),
4852
);
4953
}
5054

5155
public render(): JSX.Element {
5256
const app = this.app;
5357
if (app) {
54-
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.props.persistentWidgetId);
55-
const persistentWidgetInRoom = this.context.getRoom(persistentWidgetInRoomId);
56-
5758
return <AppTile
5859
key={app.id}
5960
app={app}
6061
fullWidth={true}
61-
room={persistentWidgetInRoom}
62+
room={this.room}
6263
userId={this.context.credentials.userId}
6364
creatorUserId={app.creatorUserId}
6465
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}

src/components/views/rooms/Stickerpicker.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,9 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
233233

234234
private sendVisibilityToWidget(visible: boolean): void {
235235
if (!this.state.stickerpickerWidget) return;
236-
const messaging = WidgetMessagingStore.instance.getMessagingForId(this.state.stickerpickerWidget.id);
236+
const messaging = WidgetMessagingStore.instance.getMessagingForUid(
237+
WidgetUtils.calcWidgetUid(this.state.stickerpickerWidget.id, null),
238+
);
237239
if (messaging && visible !== this.prevSentVisibility) {
238240
messaging.updateVisibility(visible).catch(err => {
239241
logger.error("Error updating widget visibility: ", err);

src/components/views/voip/PipView.tsx

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ interface IState {
6060

6161
// widget candidate to be displayed in the pip view.
6262
persistentWidgetId: string;
63+
persistentRoomId: string;
6364
showWidgetInPip: boolean;
6465

6566
moving: boolean;
@@ -122,6 +123,7 @@ export default class PipView extends React.Component<IProps, IState> {
122123
primaryCall: primaryCall,
123124
secondaryCall: secondaryCalls[0],
124125
persistentWidgetId: ActiveWidgetStore.instance.getPersistentWidgetId(),
126+
persistentRoomId: ActiveWidgetStore.instance.getPersistentRoomId(),
125127
showWidgetInPip: false,
126128
};
127129
}
@@ -187,7 +189,10 @@ export default class PipView extends React.Component<IProps, IState> {
187189
};
188190

189191
private onActiveWidgetStoreUpdate = (): void => {
190-
this.updateShowWidgetInPip(ActiveWidgetStore.instance.getPersistentWidgetId());
192+
this.updateShowWidgetInPip(
193+
ActiveWidgetStore.instance.getPersistentWidgetId(),
194+
ActiveWidgetStore.instance.getPersistentRoomId(),
195+
);
191196
};
192197

193198
private updateCalls = (): void => {
@@ -213,30 +218,27 @@ export default class PipView extends React.Component<IProps, IState> {
213218

214219
private onDoubleClick = (): void => {
215220
const callRoomId = this.state.primaryCall?.roomId;
216-
const widgetRoomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId);
217-
if (!!(callRoomId ?? widgetRoomId)) {
221+
if (callRoomId ?? this.state.persistentRoomId) {
218222
dis.dispatch<ViewRoomPayload>({
219223
action: Action.ViewRoom,
220-
room_id: callRoomId ?? widgetRoomId,
224+
room_id: callRoomId ?? this.state.persistentRoomId,
221225
metricsTrigger: "WebFloatingCallWindow",
222226
});
223227
}
224228
};
225229

226230
// Accepts a persistentWidgetId to be able to skip awaiting the setState for persistentWidgetId
227-
public updateShowWidgetInPip(persistentWidgetId = this.state.persistentWidgetId) {
231+
public updateShowWidgetInPip(
232+
persistentWidgetId = this.state.persistentWidgetId,
233+
persistentRoomId = this.state.persistentRoomId,
234+
) {
228235
let fromAnotherRoom = false;
229236
let notVisible = false;
230-
if (persistentWidgetId) {
231-
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(persistentWidgetId);
232-
const persistentWidgetInRoom = MatrixClientPeg.get().getRoom(persistentWidgetInRoomId);
233-
234-
// Sanity check the room - the widget may have been destroyed between render cycles, and
235-
// thus no room is associated anymore.
236-
if (persistentWidgetInRoom) {
237-
notVisible = !AppTile.isLive(persistentWidgetId);
238-
fromAnotherRoom = this.state.viewedRoomId !== persistentWidgetInRoomId;
239-
}
237+
// Sanity check the room - the widget may have been destroyed between render cycles, and
238+
// thus no room is associated anymore.
239+
if (persistentWidgetId && MatrixClientPeg.get().getRoom(persistentRoomId)) {
240+
notVisible = !AppTile.isLive(persistentWidgetId, persistentRoomId);
241+
fromAnotherRoom = this.state.viewedRoomId !== persistentRoomId;
240242
}
241243

242244
// The widget should only be shown as a persistent app (in a floating
@@ -245,7 +247,7 @@ export default class PipView extends React.Component<IProps, IState> {
245247
// containers of the room view.
246248
const showWidgetInPip = fromAnotherRoom || notVisible;
247249

248-
this.setState({ showWidgetInPip, persistentWidgetId });
250+
this.setState({ showWidgetInPip, persistentWidgetId, persistentRoomId });
249251
}
250252

251253
public render() {
@@ -269,8 +271,7 @@ export default class PipView extends React.Component<IProps, IState> {
269271
mx_CallView_pip: pipMode,
270272
mx_CallView_large: !pipMode,
271273
});
272-
const roomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId);
273-
const roomForWidget = MatrixClientPeg.get().getRoom(roomId);
274+
const roomForWidget = MatrixClientPeg.get().getRoom(this.state.persistentRoomId);
274275

275276
pipContent = ({ onStartMoving, _onResize }) =>
276277
<div className={pipViewClasses}>
@@ -281,6 +282,7 @@ export default class PipView extends React.Component<IProps, IState> {
281282
/>
282283
<PersistentApp
283284
persistentWidgetId={this.state.persistentWidgetId}
285+
persistentRoomId={this.state.persistentRoomId}
284286
pointerEvents={this.state.moving ? 'none' : undefined}
285287
/>
286288
</div>;

0 commit comments

Comments
 (0)