Skip to content

Commit 29172a4

Browse files
committed
Merge video call window review fixes (#3359) into telephony branch
# Conflicts: # src/ui/main/serverView/index.ts # src/videoCallWindow/ipc.ts
2 parents 536bdcc + 5e6f239 commit 29172a4

10 files changed

Lines changed: 798 additions & 52 deletions

File tree

src/ipc/channels.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type ChannelToArgsMap = {
3333
}
3434
) => void;
3535
'video-call-window/open-url': (url: string) => void;
36+
'video-call-window/open-in-main-window': (path: string) => void;
3637
'video-call-window/web-contents-id': (webContentsId: number) => void;
3738
'video-call-window/open-screen-picker': () => { success: boolean };
3839
'video-call-window/screen-sharing-source-responded': (source: string) => void;

src/preload.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { JitsiMeetElectron } from './jitsi/preload';
66
import { listenToNotificationsRequests } from './notifications/preload';
77
import { listenToScreenSharingRequests } from './screenSharing/preload';
88
import { RocketChatDesktop } from './servers/preload/api';
9+
import { listenToNavigateToRouteRequests } from './servers/preload/navigateToRoute';
910
import { setServerUrl } from './servers/preload/urls';
1011
import { createRendererReduxStore, listen } from './store';
1112
import { listenToTelephonyRequests } from './telephony/preload';
@@ -66,6 +67,7 @@ const start = async (): Promise<void> => {
6667
await invoke('server-view/ready');
6768

6869
listenToTelephonyRequests();
70+
listenToNavigateToRouteRequests();
6971

7072
console.log('[Rocket.Chat Desktop] waiting for RocketChatDesktop.onReady');
7173
RocketChatDesktop.onReady(() => {

src/servers/preload/api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
getInternalVideoChatWindowEnabled,
3030
openInternalVideoChatWindow,
3131
} from './internalVideoChatWindow';
32+
import { onNavigateToRoute } from './navigateToRoute';
3233
import { openInBrowser } from './openInBrowser';
3334
import { reloadServer } from './reloadServer';
3435
import {
@@ -59,6 +60,7 @@ type ExtendedIRocketChatDesktop = IRocketChatDesktop & {
5960
callback: (payload: { phoneNumber: string; rawUri: string }) => void
6061
) => void;
6162
supportedDocumentViewerFormats: () => string[];
63+
onNavigateToRoute: (callback: (path: string) => void) => void;
6264
};
6365

6466
declare global {
@@ -108,4 +110,5 @@ export const RocketChatDesktop: Window['RocketChatDesktop'] = {
108110
reloadServer,
109111
getE2ePdfPreviewSizeLimit,
110112
onTelephonyCallRequested,
113+
onNavigateToRoute,
111114
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
type IpcListener = (event: unknown, path: string) => void;
2+
3+
const ipcListeners = new Map<string, IpcListener>();
4+
const on = jest.fn((channel: string, listener: IpcListener) => {
5+
ipcListeners.set(channel, listener);
6+
});
7+
8+
jest.mock('electron', () => ({
9+
ipcRenderer: {
10+
on: (channel: string, listener: IpcListener) => on(channel, listener),
11+
},
12+
}));
13+
14+
const emit = (path: string) => {
15+
const listener = ipcListeners.get('navigate-to-route');
16+
if (!listener) throw new Error('navigate-to-route listener not registered');
17+
listener({}, path);
18+
};
19+
20+
describe('servers/preload/navigateToRoute', () => {
21+
let onNavigateToRoute: (cb: (path: string) => void) => void;
22+
let listenToNavigateToRouteRequests: () => void;
23+
24+
beforeEach(async () => {
25+
jest.resetModules();
26+
ipcListeners.clear();
27+
on.mockClear();
28+
const mod = await import('./navigateToRoute');
29+
onNavigateToRoute = mod.onNavigateToRoute;
30+
listenToNavigateToRouteRequests = mod.listenToNavigateToRouteRequests;
31+
});
32+
33+
it('registers the ipcRenderer listener only once', () => {
34+
listenToNavigateToRouteRequests();
35+
listenToNavigateToRouteRequests();
36+
expect(on).toHaveBeenCalledTimes(1);
37+
expect(on).toHaveBeenCalledWith('navigate-to-route', expect.any(Function));
38+
});
39+
40+
it('delivers the path to a callback registered before the event', () => {
41+
listenToNavigateToRouteRequests();
42+
const cb = jest.fn();
43+
onNavigateToRoute(cb);
44+
45+
emit('/channel/general');
46+
47+
expect(cb).toHaveBeenCalledWith('/channel/general');
48+
});
49+
50+
it('buffers a path that arrives before the callback registers, then flushes once', () => {
51+
listenToNavigateToRouteRequests();
52+
53+
emit('/admin/rooms');
54+
55+
const cb = jest.fn();
56+
onNavigateToRoute(cb);
57+
expect(cb).toHaveBeenCalledTimes(1);
58+
expect(cb).toHaveBeenCalledWith('/admin/rooms');
59+
60+
// The buffered path is consumed: a later registration gets nothing extra.
61+
const cb2 = jest.fn();
62+
onNavigateToRoute(cb2);
63+
expect(cb2).not.toHaveBeenCalled();
64+
});
65+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { ipcRenderer } from 'electron';
2+
3+
let navigateCallback: ((path: string) => void) | null = null;
4+
let pendingPath: string | null = null;
5+
6+
// Registered by the web client to receive in-app route changes requested by the
7+
// desktop shell (e.g. from the standalone video call window). `path` is a
8+
// server-relative route, e.g. "/channel/general".
9+
export const onNavigateToRoute = (callback: (path: string) => void): void => {
10+
navigateCallback = callback;
11+
if (pendingPath) {
12+
callback(pendingPath);
13+
pendingPath = null;
14+
}
15+
};
16+
17+
let listening = false;
18+
19+
// Relays the main-process 'navigate-to-route' event (delivered to this preload's
20+
// ipcRenderer, since the server webview is contextIsolated) to the web client's
21+
// callback. Buffers the latest path if a request arrives before the web client
22+
// has registered its handler.
23+
export const listenToNavigateToRouteRequests = (): void => {
24+
if (listening) {
25+
return;
26+
}
27+
listening = true;
28+
29+
ipcRenderer.on('navigate-to-route', (_event, path: string) => {
30+
if (navigateCallback) {
31+
navigateCallback(path);
32+
} else {
33+
pendingPath = path;
34+
}
35+
});
36+
};

src/ui/main/menuBar.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,11 @@ const createHelpMenu = createSelector(
605605
label: t('menus.toggleDevTools'),
606606
accelerator: 'CommandOrControl+Shift+D',
607607
click: async () => {
608-
const browserWindow = await getRootWindow();
608+
// Target the focused window (e.g. the video call window) so DevTools
609+
// open where the user is looking; fall back to the main window when
610+
// nothing is focused.
611+
const browserWindow =
612+
BrowserWindow.getFocusedWindow() ?? (await getRootWindow());
609613

610614
if (!browserWindow.isVisible()) {
611615
browserWindow.showInactive();

src/ui/main/serverView/index.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,20 @@ export const setupServerViewPermissionHandler = (
133133
switch (permission) {
134134
case 'media': {
135135
const { mediaTypes = [] } = details as MediaAccessPermissionRequest;
136-
await handleMediaPermissionRequest(
137-
mediaTypes as ReadonlyArray<'audio' | 'video'>,
138-
rootWindow,
139-
'recordMessage',
140-
callback
141-
);
136+
try {
137+
await handleMediaPermissionRequest(
138+
mediaTypes as ReadonlyArray<'audio' | 'video'>,
139+
rootWindow,
140+
'recordMessage',
141+
callback
142+
);
143+
} catch (error) {
144+
console.error(
145+
'Error handling media permission request in server view:',
146+
error
147+
);
148+
callback(false);
149+
}
142150
return;
143151
}
144152

@@ -518,7 +526,7 @@ export const attachGuestWebContentsEvents = async (): Promise<void> => {
518526

519527
listen(SIDE_BAR_SERVER_COPY_URL, async (action) => {
520528
const guestWebContents = getWebContentsByServerUrl(action.payload);
521-
const currentUrl = await guestWebContents?.getURL();
529+
const currentUrl = guestWebContents?.getURL();
522530
clipboard.writeText(currentUrl || '');
523531
});
524532

@@ -588,7 +596,7 @@ export const attachGuestWebContentsEvents = async (): Promise<void> => {
588596
label: t('sidebar.item.copyCurrentUrl'),
589597
click: async () => {
590598
const guestWebContents = getWebContentsByServerUrl(serverUrl);
591-
const currentUrl = await guestWebContents?.getURL();
599+
const currentUrl = guestWebContents?.getURL();
592600
clipboard.writeText(currentUrl || '');
593601
},
594602
},

0 commit comments

Comments
 (0)