diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0cc4b0cb4d..0a0634199e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -10,10 +10,10 @@ "Download Completed": "Download Completed", "Open": "Open", "Dismiss": "Dismiss", - "%(brand)s Desktop (%(platformName)s)": "%(brand)s Desktop (%(platformName)s)", + "%(brand)s Desktop: %(platformName)s": "%(brand)s Desktop: %(platformName)s", "Go to your browser to complete Sign In": "Go to your browser to complete Sign In", "Unknown device": "Unknown device", - "%(appName)s (%(browserName)s, %(osName)s)": "%(appName)s (%(browserName)s, %(osName)s)", + "%(appName)s: %(browserName)s on %(osName)s": "%(appName)s: %(browserName)s on %(osName)s", "Powered by Matrix": "Powered by Matrix", "Use %(brand)s on mobile": "Use %(brand)s on mobile", "Unsupported browser": "Unsupported browser", diff --git a/src/vector/platform/ElectronPlatform.tsx b/src/vector/platform/ElectronPlatform.tsx index d50accc1c1..635660d43f 100644 --- a/src/vector/platform/ElectronPlatform.tsx +++ b/src/vector/platform/ElectronPlatform.tsx @@ -45,7 +45,6 @@ import VectorBasePlatform from './VectorBasePlatform'; import { SeshatIndexManager } from "./SeshatIndexManager"; import { IPCManager } from "./IPCManager"; -const electron = window.electron; const isMac = navigator.platform.toUpperCase().includes('MAC'); function platformFriendlyName(): string { @@ -70,7 +69,7 @@ function platformFriendlyName(): string { function onAction(payload: ActionPayload): void { // Whitelist payload actions, no point sending most across if (['call_state'].includes(payload.action)) { - electron.send('app_onAction', payload); + window.electron.send('app_onAction', payload); } } @@ -103,7 +102,7 @@ export default class ElectronPlatform extends VectorBasePlatform { false if there is not or the error if one is encountered */ - electron.on('check_updates', (event, status) => { + window.electron.on('check_updates', (event, status) => { dis.dispatch({ action: Action.CheckUpdates, ...getUpdateCheckStatus(status), @@ -111,27 +110,27 @@ export default class ElectronPlatform extends VectorBasePlatform { }); // try to flush the rageshake logs to indexeddb before quit. - electron.on('before-quit', function() { + window.electron.on('before-quit', function() { logger.log('element-desktop closing'); rageshake.flush(); }); - electron.on('update-downloaded', this.onUpdateDownloaded); + window.electron.on('update-downloaded', this.onUpdateDownloaded); - electron.on('preferences', () => { + window.electron.on('preferences', () => { dis.fire(Action.ViewUserSettings); }); - electron.on('userDownloadCompleted', (ev, { id, name }) => { + window.electron.on('userDownloadCompleted', (ev, { id, name }) => { const key = `DOWNLOAD_TOAST_${id}`; const onAccept = () => { - electron.send('userDownloadAction', { id, open: true }); + window.electron.send('userDownloadAction', { id, open: true }); ToastStore.sharedInstance().dismissToast(key); }; const onDismiss = () => { - electron.send('userDownloadAction', { id }); + window.electron.send('userDownloadAction', { id }); }; ToastStore.sharedInstance().addOrReplaceToast({ @@ -187,7 +186,7 @@ export default class ElectronPlatform extends VectorBasePlatform { if (this.notificationCount === count) return; super.setNotificationCount(count); - electron.send('setBadgeCount', count); + window.electron.send('setBadgeCount', count); } public supportsNotifications(): boolean { @@ -233,7 +232,7 @@ export default class ElectronPlatform extends VectorBasePlatform { } public loudNotification(ev: MatrixEvent, room: Room) { - electron.send('loudNotification'); + window.electron.send('loudNotification'); } public needsUrlTooltips(): boolean { @@ -269,19 +268,19 @@ export default class ElectronPlatform extends VectorBasePlatform { public startUpdateCheck() { super.startUpdateCheck(); - electron.send('check_updates'); + window.electron.send('check_updates'); } public installUpdate() { // IPC to the main process to install the update, since quitAndInstall // doesn't fire the before-quit event so the main process needs to know // it should exit. - electron.send('install_update'); + window.electron.send('install_update'); } public getDefaultDeviceDisplayName(): string { const brand = SdkConfig.get().brand; - return _t('%(brand)s Desktop (%(platformName)s)', { + return _t('%(brand)s Desktop: %(platformName)s', { brand, platformName: platformFriendlyName(), }); diff --git a/src/vector/platform/IPCManager.ts b/src/vector/platform/IPCManager.ts index d25fe0af63..6e9abf16f2 100644 --- a/src/vector/platform/IPCManager.ts +++ b/src/vector/platform/IPCManager.ts @@ -19,8 +19,6 @@ import { logger } from "matrix-js-sdk/src/logger"; import { ElectronChannel } from "../../@types/global"; -const electron = window.electron; - interface IPCPayload { id?: number; error?: string; @@ -35,7 +33,7 @@ export class IPCManager { private readonly sendChannel: ElectronChannel = "ipcCall", private readonly recvChannel: ElectronChannel = "ipcReply", ) { - electron.on(this.recvChannel, this.onIpcReply); + window.electron.on(this.recvChannel, this.onIpcReply); } public async call(name: string, ...args: any[]): Promise { diff --git a/src/vector/platform/WebPlatform.ts b/src/vector/platform/WebPlatform.ts index 77be97ce49..110358a479 100644 --- a/src/vector/platform/WebPlatform.ts +++ b/src/vector/platform/WebPlatform.ts @@ -210,7 +210,7 @@ export default class WebPlatform extends VectorBasePlatform { let osName = ua.getOS().name || "unknown OS"; // Stylise the value from the parser to match Apple's current branding. if (osName === "Mac OS") osName = "macOS"; - return _t('%(appName)s (%(browserName)s, %(osName)s)', { + return _t('%(appName)s: %(browserName)s on %(osName)s', { appName, browserName, osName, diff --git a/test/unit-tests/vector/platform/ElectronPlatform-test.ts b/test/unit-tests/vector/platform/ElectronPlatform-test.ts new file mode 100644 index 0000000000..b76e045043 --- /dev/null +++ b/test/unit-tests/vector/platform/ElectronPlatform-test.ts @@ -0,0 +1,279 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import request from 'browser-request'; +import EventEmitter from 'events'; +import { logger } from 'matrix-js-sdk/src/logger'; +import { MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk/src/matrix'; +import { UpdateCheckStatus } from 'matrix-react-sdk/src/BasePlatform'; +import { Action } from 'matrix-react-sdk/src/dispatcher/actions'; +import dispatcher from 'matrix-react-sdk/src/dispatcher/dispatcher'; +import { MatrixClientPeg } from 'matrix-react-sdk/src/MatrixClientPeg'; +import * as rageshake from 'matrix-react-sdk/src/rageshake/rageshake'; + +import ElectronPlatform from '../../../../src/vector/platform/ElectronPlatform'; + +jest.mock('matrix-react-sdk/src/rageshake/rageshake', () => ({ + flush: jest.fn() +})) + + +describe('ElectronPlatform', () => { + const defaultUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'; + const mockElectron = { + on: jest.fn(), + send: jest.fn() + }; + + const dispatchSpy = jest.spyOn(dispatcher, 'dispatch'); + const dispatchFireSpy = jest.spyOn(dispatcher, 'fire'); + const logSpy = jest.spyOn(logger, 'log').mockImplementation(() => {}); + + const userId = '@alice:server.org'; + const deviceId = 'device-id'; + + window.electron = mockElectron; + beforeEach(() => { + window.electron = mockElectron; + jest.clearAllMocks(); + delete window.navigator; + window.navigator = { userAgent: defaultUserAgent } as unknown as Navigator; + }); + + const getElectronEventHandlerCall = (eventType: string): [type: string, handler: Function] | undefined => + mockElectron.on.mock.calls.find(([type]) => type === eventType); + + it('flushes rageshake before quitting', () => { + new ElectronPlatform(); + const [event, handler] = getElectronEventHandlerCall('before-quit'); + // correct event bound + expect(event).toBeTruthy(); + + handler(); + + expect(logSpy).toHaveBeenCalled(); + expect(rageshake.flush).toHaveBeenCalled(); + }); + + it('dispatches view settings action on preferences event', () => { + new ElectronPlatform(); + const [event, handler] = getElectronEventHandlerCall('preferences'); + // correct event bound + expect(event).toBeTruthy(); + + handler(); + + expect(dispatchFireSpy).toHaveBeenCalledWith(Action.ViewUserSettings); + }); + + describe('updates', () => { + it('dispatches on check updates action', () => { + new ElectronPlatform(); + const [event, handler] = getElectronEventHandlerCall('check_updates'); + // correct event bound + expect(event).toBeTruthy(); + + handler({}, true); + expect(dispatchSpy).toHaveBeenCalledWith({ + action: Action.CheckUpdates, + status: UpdateCheckStatus.Downloading + }) + }); + + it('dispatches on check updates action when update not available', () => { + new ElectronPlatform(); + const [, handler] = getElectronEventHandlerCall('check_updates'); + + handler({}, false); + expect(dispatchSpy).toHaveBeenCalledWith({ + action: Action.CheckUpdates, + status: UpdateCheckStatus.NotAvailable + }) + }); + + it('starts update check', () => { + const platform = new ElectronPlatform(); + platform.startUpdateCheck(); + expect(mockElectron.send).toHaveBeenCalledWith('check_updates') + }); + + it('installs update', () => { + const platform = new ElectronPlatform(); + platform.installUpdate(); + expect(mockElectron.send).toHaveBeenCalledWith('install_update') + }); + }); + + + it('returns human readable name', () => { + const platform = new ElectronPlatform(); + expect(platform.getHumanReadableName()).toEqual('Electron Platform'); + }); + + describe("getDefaultDeviceDisplayName", () => { + it.each([[ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", + "Element Desktop: macOS", + ], + [ + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) electron/1.0.0 Chrome/53.0.2785.113 Electron/1.4.3 Safari/537.36", + "Element Desktop: Windows", + ], + [ + "Mozilla/5.0 (X11; Linux i686; rv:21.0) Gecko/20100101 Firefox/21.0", + "Element Desktop: Linux", + ], + [ + "Mozilla/5.0 (X11; FreeBSD i686; rv:21.0) Gecko/20100101 Firefox/21.0", + "Element Desktop: FreeBSD", + ], + [ + "Mozilla/5.0 (X11; OpenBSD i686; rv:21.0) Gecko/20100101 Firefox/21.0", + "Element Desktop: OpenBSD", + ], + [ + "Mozilla/5.0 (X11; SunOS i686; rv:21.0) Gecko/20100101 Firefox/21.0", + "Element Desktop: SunOS", + ], + [ + "custom user agent", + "Element Desktop: Unknown", + ], + + ])("%s = %s", (userAgent, result) => { + delete window.navigator; + window.navigator = { userAgent } as unknown as Navigator; + const platform = new ElectronPlatform(); + expect(platform.getDefaultDeviceDisplayName()).toEqual(result); + }); + }); + + it('returns true for needsUrlTooltips', () => { + const platform = new ElectronPlatform(); + expect(platform.needsUrlTooltips()).toBe(true); + }); + + it('should override browser shortcuts', () => { + const platform = new ElectronPlatform(); + expect(platform.overrideBrowserShortcuts()).toBe(true); + }); + + it('allows overriding native context menus', () => { + const platform = new ElectronPlatform(); + expect(platform.allowOverridingNativeContextMenus()).toBe(true); + }); + + it('indicates support for desktop capturer', () => { + const platform = new ElectronPlatform(); + expect(platform.supportsDesktopCapturer()).toBe(true); + }); + + it('indicates no support for jitsi screensharing', () => { + const platform = new ElectronPlatform(); + expect(platform.supportsJitsiScreensharing()).toBe(false); + }); + + describe('notifications', () => { + it('indicates support for notifications', () => { + const platform = new ElectronPlatform(); + expect(platform.supportsNotifications()).toBe(true); + }); + + it('may send notifications', () => { + const platform = new ElectronPlatform(); + expect(platform.maySendNotifications()).toBe(true); + }); + + it('pretends to request notification permission', async () => { + const platform = new ElectronPlatform(); + const result = await platform.requestNotificationPermission(); + expect(result).toEqual('granted'); + }); + + it('creates a loud notification', async () => { + const platform = new ElectronPlatform(); + platform.loudNotification(new MatrixEvent(), new Room('!room:server', {} as any, userId)); + expect(mockElectron.send).toHaveBeenCalledWith('loudNotification'); + }); + + it('sets notification count when count is changing', async () => { + const platform = new ElectronPlatform(); + platform.setNotificationCount(0); + // not called because matches internal notificaiton count + expect(mockElectron.send).not.toHaveBeenCalledWith('setBadgeCount', 0); + platform.setNotificationCount(1); + expect(mockElectron.send).toHaveBeenCalledWith('setBadgeCount', 1); + }); + }); + + describe('spellcheck', () => { + it('indicates support for spellcheck settings', () => { + const platform = new ElectronPlatform(); + expect(platform.supportsSpellCheckSettings()).toBe(true); + }); + + it('gets available spellcheck languages', () => { + const platform = new ElectronPlatform(); + mockElectron.send.mockClear(); + platform.getAvailableSpellCheckLanguages(); + + const [channel, { name }] = mockElectron.send.mock.calls[0]; + expect(channel).toEqual("ipcCall"); + expect(name).toEqual('getAvailableSpellCheckLanguages') + }); + }); + + describe('pickle key', () => { + it('makes correct ipc call to get pickle key', () => { + const platform = new ElectronPlatform(); + mockElectron.send.mockClear(); + platform.getPickleKey(userId, deviceId); + + const [, { name, args }] = mockElectron.send.mock.calls[0]; + expect(name).toEqual('getPickleKey') + expect(args).toEqual([userId, deviceId]) + }); + + it('makes correct ipc call to create pickle key', () => { + const platform = new ElectronPlatform(); + mockElectron.send.mockClear(); + platform.createPickleKey(userId, deviceId); + + const [, { name, args }] = mockElectron.send.mock.calls[0]; + expect(name).toEqual('createPickleKey') + expect(args).toEqual([userId, deviceId]) + }); + + it('makes correct ipc call to destroy pickle key', () => { + const platform = new ElectronPlatform(); + mockElectron.send.mockClear(); + platform.destroyPickleKey(userId, deviceId); + + const [, { name, args }] = mockElectron.send.mock.calls[0]; + expect(name).toEqual('destroyPickleKey') + expect(args).toEqual([userId, deviceId]) + }); + }); + + describe('versions', () => { + it('calls install update', () => { + const platform = new ElectronPlatform(); + platform.installUpdate(); + + expect(mockElectron.send).toHaveBeenCalledWith('install_update'); + }); + }); +}); diff --git a/test/unit-tests/vector/platform/WebPlatform-test.ts b/test/unit-tests/vector/platform/WebPlatform-test.ts index ced944e688..7889adabe9 100644 --- a/test/unit-tests/vector/platform/WebPlatform-test.ts +++ b/test/unit-tests/vector/platform/WebPlatform-test.ts @@ -65,7 +65,7 @@ describe('WebPlatform', () => { it.each([[ "https://develop.element.io/#/room/!foo:bar", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", - "develop.element.io (Chrome, macOS)", + "develop.element.io: Chrome on macOS", ]])("%s & %s = %s", (url, userAgent, result) => { delete window.navigator; window.navigator = { userAgent } as unknown as Navigator;