diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index aef27979d6..0cc4b0cb4d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -14,7 +14,6 @@ "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)", - "You need to be using HTTPS to place a screen-sharing call.": "You need to be using HTTPS to place a screen-sharing call.", "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 d41d239b5c..9c1bc3b9bc 100644 --- a/src/vector/platform/ElectronPlatform.tsx +++ b/src/vector/platform/ElectronPlatform.tsx @@ -18,13 +18,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { UpdateCheckStatus } from "matrix-react-sdk/src/BasePlatform"; -import BaseEventIndexManager, { - ICrawlerCheckpoint, - IEventAndProfile, - IIndexStats, - ISearchArgs, -} from 'matrix-react-sdk/src/indexing/BaseEventIndexManager'; +import { UpdateCheckStatus, UpdateStatus } from "matrix-react-sdk/src/BasePlatform"; +import BaseEventIndexManager from 'matrix-react-sdk/src/indexing/BaseEventIndexManager'; import dis from 'matrix-react-sdk/src/dispatcher/dispatcher'; import { _t } from 'matrix-react-sdk/src/languageHandler'; import SdkConfig from 'matrix-react-sdk/src/SdkConfig'; @@ -43,11 +38,12 @@ import { showToast as showUpdateToast } from "matrix-react-sdk/src/toasts/Update import { CheckUpdatesPayload } from "matrix-react-sdk/src/dispatcher/payloads/CheckUpdatesPayload"; import ToastStore from "matrix-react-sdk/src/stores/ToastStore"; import GenericExpiringToast from "matrix-react-sdk/src/components/views/toasts/GenericExpiringToast"; -import { IMatrixProfile, IEventWithRoomId as IMatrixEvent, IResultRoomEvents } from "matrix-js-sdk/src/@types/search"; import { logger } from "matrix-js-sdk/src/logger"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import VectorBasePlatform from './VectorBasePlatform'; +import { SeshatIndexManager } from "./SeshatIndexManager"; +import { IPCManager } from "./IPCManager"; const electron = window.electron; const isMac = navigator.platform.toUpperCase().includes('MAC'); @@ -71,14 +67,14 @@ function platformFriendlyName(): string { } } -function _onAction(payload: ActionPayload) { +function onAction(payload: ActionPayload): void { // Whitelist payload actions, no point sending most across if (['call_state'].includes(payload.action)) { electron.send('app_onAction', payload); } } -function getUpdateCheckStatus(status: boolean | string) { +function getUpdateCheckStatus(status: boolean | string): UpdateStatus { if (status === true) { return { status: UpdateCheckStatus.Downloading }; } else if (status === false) { @@ -91,139 +87,16 @@ function getUpdateCheckStatus(status: boolean | string) { } } -interface IPCPayload { - id?: number; - error?: string; - reply?: any; -} - -class SeshatIndexManager extends BaseEventIndexManager { - private pendingIpcCalls: Record = {}; - private nextIpcCallId = 0; - - constructor() { - super(); - - electron.on('seshatReply', this.onIpcReply); - } - - private async ipcCall(name: string, ...args: any[]): Promise { - // TODO this should be moved into the preload.js file. - const ipcCallId = ++this.nextIpcCallId; - return new Promise((resolve, reject) => { - this.pendingIpcCalls[ipcCallId] = { resolve, reject }; - window.electron.send('seshat', { id: ipcCallId, name, args }); - }); - } - - private onIpcReply = (ev: {}, payload: IPCPayload) => { - if (payload.id === undefined) { - logger.warn("Ignoring IPC reply with no ID"); - return; - } - - if (this.pendingIpcCalls[payload.id] === undefined) { - logger.warn("Unknown IPC payload ID: " + payload.id); - return; - } - - const callbacks = this.pendingIpcCalls[payload.id]; - delete this.pendingIpcCalls[payload.id]; - if (payload.error) { - callbacks.reject(payload.error); - } else { - callbacks.resolve(payload.reply); - } - }; - - async supportsEventIndexing(): Promise { - return this.ipcCall('supportsEventIndexing'); - } - - async initEventIndex(userId: string, deviceId: string): Promise { - return this.ipcCall('initEventIndex', userId, deviceId); - } - - async addEventToIndex(ev: IMatrixEvent, profile: IMatrixProfile): Promise { - return this.ipcCall('addEventToIndex', ev, profile); - } - - async deleteEvent(eventId: string): Promise { - return this.ipcCall('deleteEvent', eventId); - } - - async isEventIndexEmpty(): Promise { - return this.ipcCall('isEventIndexEmpty'); - } - - async isRoomIndexed(roomId: string): Promise { - return this.ipcCall('isRoomIndexed', roomId); - } - - async commitLiveEvents(): Promise { - return this.ipcCall('commitLiveEvents'); - } - - async searchEventIndex(searchConfig: ISearchArgs): Promise { - return this.ipcCall('searchEventIndex', searchConfig); - } - - async addHistoricEvents( - events: IEventAndProfile[], - checkpoint: ICrawlerCheckpoint | null, - oldCheckpoint: ICrawlerCheckpoint | null, - ): Promise { - return this.ipcCall('addHistoricEvents', events, checkpoint, oldCheckpoint); - } - - async addCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise { - return this.ipcCall('addCrawlerCheckpoint', checkpoint); - } - - async removeCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise { - return this.ipcCall('removeCrawlerCheckpoint', checkpoint); - } - - async loadFileEvents(args): Promise { - return this.ipcCall('loadFileEvents', args); - } - - async loadCheckpoints(): Promise { - return this.ipcCall('loadCheckpoints'); - } - - async closeEventIndex(): Promise { - return this.ipcCall('closeEventIndex'); - } - - async getStats(): Promise { - return this.ipcCall('getStats'); - } - - async getUserVersion(): Promise { - return this.ipcCall('getUserVersion'); - } - - async setUserVersion(version: number): Promise { - return this.ipcCall('setUserVersion', version); - } - - async deleteEventIndex(): Promise { - return this.ipcCall('deleteEventIndex'); - } -} - export default class ElectronPlatform extends VectorBasePlatform { - private eventIndexManager: BaseEventIndexManager = new SeshatIndexManager(); - private pendingIpcCalls: Record = {}; - private nextIpcCallId = 0; + private readonly ipc = new IPCManager("ipcCall", "ipcReply"); + private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager(); // this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile - private ssoID: string = randomString(32); + private readonly ssoID: string = randomString(32); constructor() { super(); - dis.register(_onAction); + dis.register(onAction); /* IPC Call `check_updates` returns: true if there is an update available @@ -243,7 +116,6 @@ export default class ElectronPlatform extends VectorBasePlatform { rageshake.flush(); }); - electron.on('ipcReply', this.onIpcReply); electron.on('update-downloaded', this.onUpdateDownloaded); electron.on('preferences', () => { @@ -278,14 +150,14 @@ export default class ElectronPlatform extends VectorBasePlatform { }); }); - this.ipcCall("startSSOFlow", this.ssoID); + this.ipc.call("startSSOFlow", this.ssoID); } - async getConfig(): Promise { - return this.ipcCall('getConfig'); + public async getConfig(): Promise { + return this.ipc.call('getConfig'); } - onUpdateDownloaded = async (ev, { releaseNotes, releaseName }) => { + private onUpdateDownloaded = async (ev, { releaseNotes, releaseName }) => { dis.dispatch({ action: Action.CheckUpdates, status: UpdateCheckStatus.Ready, @@ -295,7 +167,7 @@ export default class ElectronPlatform extends VectorBasePlatform { } }; - getHumanReadableName(): string { + public getHumanReadableName(): string { return 'Electron Platform'; // no translation required: only used for analytics } @@ -303,7 +175,7 @@ export default class ElectronPlatform extends VectorBasePlatform { * Return true if platform supports multi-language * spell-checking, otherwise false. */ - supportsMultiLanguageSpellCheck(): boolean { + public supportsMultiLanguageSpellCheck(): boolean { // Electron uses OS spell checking on macOS, so no need for in-app options if (isMac) return false; return true; @@ -320,15 +192,21 @@ export default class ElectronPlatform extends VectorBasePlatform { electron.send('setBadgeCount', count); } - supportsNotifications(): boolean { + public supportsNotifications(): boolean { return true; } - maySendNotifications(): boolean { + public maySendNotifications(): boolean { return true; } - displayNotification(title: string, msg: string, avatarUrl: string, room: Room, ev?: MatrixEvent): Notification { + public displayNotification( + title: string, + msg: string, + avatarUrl: string, + room: Room, + ev?: MatrixEvent, + ): Notification { // GNOME notification spec parses HTML tags for styling... // Electron Docs state all supported linux notification systems follow this markup spec // https://github.com/electron/electron/blob/master/docs/tutorial/desktop-environment-integration.md#linux @@ -350,100 +228,56 @@ export default class ElectronPlatform extends VectorBasePlatform { const handler = notification.onclick as Function; notification.onclick = () => { handler?.(); - this.ipcCall('focusWindow'); + this.ipc.call('focusWindow'); }; return notification; } - loudNotification(ev: MatrixEvent, room: Room) { + public loudNotification(ev: MatrixEvent, room: Room) { electron.send('loudNotification'); } - async getAppVersion(): Promise { - return this.ipcCall('getAppVersion'); + public async getAppVersion(): Promise { + return this.ipc.call('getAppVersion'); } - supportsAutoLaunch(): boolean { - return true; + public supportsSetting(settingName?: string): boolean { + switch (settingName) { + case "Electron.showTrayIcon": // Things other than Mac support tray icons + case "Electron.alwaysShowMenuBar": // This isn't relevant on Mac as Menu bars don't live in the app window + return !isMac; + default: + return true; + } } - async getAutoLaunchEnabled(): Promise { - return this.ipcCall('getAutoLaunchEnabled'); + public getSettingValue(settingName: string): Promise { + return this.ipc.call("getSettingValue", settingName); } - async setAutoLaunchEnabled(enabled: boolean): Promise { - return this.ipcCall('setAutoLaunchEnabled', enabled); - } - - supportsWarnBeforeExit(): boolean { - return true; - } - - async shouldWarnBeforeExit(): Promise { - return this.ipcCall('shouldWarnBeforeExit'); - } - - async setWarnBeforeExit(enabled: boolean): Promise { - return this.ipcCall('setWarnBeforeExit', enabled); - } - - supportsAutoHideMenuBar(): boolean { - // This is irelevant on Mac as Menu bars don't live in the app window - return !isMac; - } - - async getAutoHideMenuBarEnabled(): Promise { - return this.ipcCall('getAutoHideMenuBarEnabled'); - } - - async setAutoHideMenuBarEnabled(enabled: boolean): Promise { - return this.ipcCall('setAutoHideMenuBarEnabled', enabled); - } - - supportsMinimizeToTray(): boolean { - // Things other than Mac support tray icons - return !isMac; - } - - async getMinimizeToTrayEnabled(): Promise { - return this.ipcCall('getMinimizeToTrayEnabled'); - } - - async setMinimizeToTrayEnabled(enabled: boolean): Promise { - return this.ipcCall('setMinimizeToTrayEnabled', enabled); - } - - public supportsTogglingHardwareAcceleration(): boolean { - return true; - } - - public async getHardwareAccelerationEnabled(): Promise { - return this.ipcCall('getHardwareAccelerationEnabled'); - } - - public async setHardwareAccelerationEnabled(enabled: boolean): Promise { - return this.ipcCall('setHardwareAccelerationEnabled', enabled); + public setSettingValue(settingName: string, value: any): Promise { + return this.ipc.call("setSettingValue", settingName, value); } async canSelfUpdate(): Promise { - const feedUrl = await this.ipcCall('getUpdateFeedUrl'); + const feedUrl = await this.ipc.call('getUpdateFeedUrl'); return Boolean(feedUrl); } - startUpdateCheck() { + public startUpdateCheck() { super.startUpdateCheck(); electron.send('check_updates'); } - installUpdate() { + 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'); } - getDefaultDeviceDisplayName(): string { + public getDefaultDeviceDisplayName(): string { const brand = SdkConfig.get().brand; return _t('%(brand)s Desktop (%(platformName)s)', { brand, @@ -451,86 +285,58 @@ export default class ElectronPlatform extends VectorBasePlatform { }); } - screenCaptureErrorString(): string | null { - return null; - } - - requestNotificationPermission(): Promise { + public requestNotificationPermission(): Promise { return Promise.resolve('granted'); } - reload() { + public reload() { window.location.reload(); } - private async ipcCall(name: string, ...args: any[]): Promise { - const ipcCallId = ++this.nextIpcCallId; - return new Promise((resolve, reject) => { - this.pendingIpcCalls[ipcCallId] = { resolve, reject }; - window.electron.send('ipcCall', { id: ipcCallId, name, args }); - // Maybe add a timeout to these? Probably not necessary. - }); - } - - private onIpcReply = (ev, payload) => { - if (payload.id === undefined) { - logger.warn("Ignoring IPC reply with no ID"); - return; - } - - if (this.pendingIpcCalls[payload.id] === undefined) { - logger.warn("Unknown IPC payload ID: " + payload.id); - return; - } - - const callbacks = this.pendingIpcCalls[payload.id]; - delete this.pendingIpcCalls[payload.id]; - if (payload.error) { - callbacks.reject(payload.error); - } else { - callbacks.resolve(payload.reply); - } - }; - - getEventIndexingManager(): BaseEventIndexManager | null { + public getEventIndexingManager(): BaseEventIndexManager | null { return this.eventIndexManager; } - async setLanguage(preferredLangs: string[]) { - return this.ipcCall('setLanguage', preferredLangs); + public async setLanguage(preferredLangs: string[]) { + return this.ipc.call('setLanguage', preferredLangs); } - setSpellCheckLanguages(preferredLangs: string[]) { - this.ipcCall('setSpellCheckLanguages', preferredLangs).catch(error => { + public setSpellCheckLanguages(preferredLangs: string[]) { + this.ipc.call('setSpellCheckLanguages', preferredLangs).catch(error => { logger.log("Failed to send setSpellCheckLanguages IPC to Electron"); logger.error(error); }); } - async getSpellCheckLanguages(): Promise { - return this.ipcCall('getSpellCheckLanguages'); + public async getSpellCheckLanguages(): Promise { + return this.ipc.call('getSpellCheckLanguages'); } - async getDesktopCapturerSources(options: GetSourcesOptions): Promise> { - return this.ipcCall('getDesktopCapturerSources', options); + public async getDesktopCapturerSources(options: GetSourcesOptions): Promise> { + return this.ipc.call('getDesktopCapturerSources', options); } - supportsDesktopCapturer(): boolean { + public supportsDesktopCapturer(): boolean { return true; } - async getAvailableSpellCheckLanguages(): Promise { - return this.ipcCall('getAvailableSpellCheckLanguages'); + public async getAvailableSpellCheckLanguages(): Promise { + return this.ipc.call('getAvailableSpellCheckLanguages'); } - getSSOCallbackUrl(fragmentAfterLogin: string): URL { + public getSSOCallbackUrl(fragmentAfterLogin: string): URL { const url = super.getSSOCallbackUrl(fragmentAfterLogin); url.protocol = "element"; url.searchParams.set("element-desktop-ssoid", this.ssoID); return url; } - startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas", fragmentAfterLogin: string, idpId?: string) { + public startSingleSignOn( + mxClient: MatrixClient, + loginType: "sso" | "cas", + fragmentAfterLogin: string, + idpId?: string, + ) { // this will get intercepted by electron-main will-navigate super.startSingleSignOn(mxClient, loginType, fragmentAfterLogin, idpId); Modal.createTrackedDialog('Electron', 'SSO', InfoDialog, { @@ -540,16 +346,16 @@ export default class ElectronPlatform extends VectorBasePlatform { } public navigateForwardBack(back: boolean): void { - this.ipcCall(back ? "navigateBack" : "navigateForward"); + this.ipc.call(back ? "navigateBack" : "navigateForward"); } public overrideBrowserShortcuts(): boolean { return true; } - async getPickleKey(userId: string, deviceId: string): Promise { + public async getPickleKey(userId: string, deviceId: string): Promise { try { - return await this.ipcCall('getPickleKey', userId, deviceId); + return await this.ipc.call('getPickleKey', userId, deviceId); } catch (e) { // if we can't connect to the password storage, assume there's no // pickle key @@ -557,9 +363,9 @@ export default class ElectronPlatform extends VectorBasePlatform { } } - async createPickleKey(userId: string, deviceId: string): Promise { + public async createPickleKey(userId: string, deviceId: string): Promise { try { - return await this.ipcCall('createPickleKey', userId, deviceId); + return await this.ipc.call('createPickleKey', userId, deviceId); } catch (e) { // if we can't connect to the password storage, assume there's no // pickle key @@ -567,9 +373,9 @@ export default class ElectronPlatform extends VectorBasePlatform { } } - async destroyPickleKey(userId: string, deviceId: string): Promise { + public async destroyPickleKey(userId: string, deviceId: string): Promise { try { - await this.ipcCall('destroyPickleKey', userId, deviceId); + await this.ipc.call('destroyPickleKey', userId, deviceId); } catch (e) {} } } diff --git a/src/vector/platform/IPCManager.ts b/src/vector/platform/IPCManager.ts new file mode 100644 index 0000000000..c0ceda64ea --- /dev/null +++ b/src/vector/platform/IPCManager.ts @@ -0,0 +1,70 @@ +/* +Copyright 2022 New Vector Ltd + +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 { defer, IDeferred } from 'matrix-js-sdk/src/utils'; +import { logger } from "matrix-js-sdk/src/logger"; + +import { ElectronChannel } from "../../@types/global"; + +const electron = window.electron; + +interface IPCPayload { + id?: number; + error?: string; + reply?: any; +} + +export class IPCManager { + private pendingIpcCalls: { [ipcCallId: number]: IDeferred } = {}; + private nextIpcCallId = 0; + + public constructor( + private readonly sendChannel: ElectronChannel = "ipcCall", + private readonly recvChannel: ElectronChannel = "ipcReply", + ) { + electron.on(this.recvChannel, this.onIpcReply); + } + + public async call(name: string, ...args: any[]): Promise { + // TODO this should be moved into the preload.js file. + const ipcCallId = ++this.nextIpcCallId; + const deferred = defer(); + this.pendingIpcCalls[ipcCallId] = deferred; + // Maybe add a timeout to these? Probably not necessary. + window.electron.send(this.sendChannel, { id: ipcCallId, name, args }); + return deferred.promise; + } + + private onIpcReply = (ev: {}, payload: IPCPayload): void => { + if (payload.id === undefined) { + logger.warn("Ignoring IPC reply with no ID"); + return; + } + + if (this.pendingIpcCalls[payload.id] === undefined) { + logger.warn("Unknown IPC payload ID: " + payload.id); + return; + } + + const callbacks = this.pendingIpcCalls[payload.id]; + delete this.pendingIpcCalls[payload.id]; + if (payload.error) { + callbacks.reject(payload.error); + } else { + callbacks.resolve(payload.reply); + } + }; +} diff --git a/src/vector/platform/PWAPlatform.ts b/src/vector/platform/PWAPlatform.ts index de9d9884f6..ea0c9cf168 100644 --- a/src/vector/platform/PWAPlatform.ts +++ b/src/vector/platform/PWAPlatform.ts @@ -19,7 +19,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import WebPlatform from "./WebPlatform"; export default class PWAPlatform extends WebPlatform { - setNotificationCount(count: number) { + public setNotificationCount(count: number): void { if (!navigator.setAppBadge) return super.setNotificationCount(count); if (this.notificationCount === count) return; this.notificationCount = count; diff --git a/src/vector/platform/SeshatIndexManager.ts b/src/vector/platform/SeshatIndexManager.ts new file mode 100644 index 0000000000..2f08f49296 --- /dev/null +++ b/src/vector/platform/SeshatIndexManager.ts @@ -0,0 +1,105 @@ +/* +Copyright 2022 New Vector Ltd + +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 BaseEventIndexManager, { + ICrawlerCheckpoint, + IEventAndProfile, + IIndexStats, + ISearchArgs, +} from 'matrix-react-sdk/src/indexing/BaseEventIndexManager'; +import { IMatrixProfile, IEventWithRoomId as IMatrixEvent, IResultRoomEvents } from "matrix-js-sdk/src/@types/search"; + +import { IPCManager } from "./IPCManager"; + +export class SeshatIndexManager extends BaseEventIndexManager { + private readonly ipc = new IPCManager("seshat", "seshatReply"); + + public async supportsEventIndexing(): Promise { + return this.ipc.call('supportsEventIndexing'); + } + + public async initEventIndex(userId: string, deviceId: string): Promise { + return this.ipc.call('initEventIndex', userId, deviceId); + } + + public async addEventToIndex(ev: IMatrixEvent, profile: IMatrixProfile): Promise { + return this.ipc.call('addEventToIndex', ev, profile); + } + + public async deleteEvent(eventId: string): Promise { + return this.ipc.call('deleteEvent', eventId); + } + + public async isEventIndexEmpty(): Promise { + return this.ipc.call('isEventIndexEmpty'); + } + + public async isRoomIndexed(roomId: string): Promise { + return this.ipc.call('isRoomIndexed', roomId); + } + + public async commitLiveEvents(): Promise { + return this.ipc.call('commitLiveEvents'); + } + + public async searchEventIndex(searchConfig: ISearchArgs): Promise { + return this.ipc.call('searchEventIndex', searchConfig); + } + + public async addHistoricEvents( + events: IEventAndProfile[], + checkpoint: ICrawlerCheckpoint | null, + oldCheckpoint: ICrawlerCheckpoint | null, + ): Promise { + return this.ipc.call('addHistoricEvents', events, checkpoint, oldCheckpoint); + } + + public async addCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise { + return this.ipc.call('addCrawlerCheckpoint', checkpoint); + } + + public async removeCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise { + return this.ipc.call('removeCrawlerCheckpoint', checkpoint); + } + + public async loadFileEvents(args): Promise { + return this.ipc.call('loadFileEvents', args); + } + + public async loadCheckpoints(): Promise { + return this.ipc.call('loadCheckpoints'); + } + + public async closeEventIndex(): Promise { + return this.ipc.call('closeEventIndex'); + } + + public async getStats(): Promise { + return this.ipc.call('getStats'); + } + + public async getUserVersion(): Promise { + return this.ipc.call('getUserVersion'); + } + + public async setUserVersion(version: number): Promise { + return this.ipc.call('setUserVersion', version); + } + + public async deleteEventIndex(): Promise { + return this.ipc.call('deleteEventIndex'); + } +} diff --git a/src/vector/platform/VectorBasePlatform.ts b/src/vector/platform/VectorBasePlatform.ts index 382fd62604..b6e78629eb 100644 --- a/src/vector/platform/VectorBasePlatform.ts +++ b/src/vector/platform/VectorBasePlatform.ts @@ -30,11 +30,11 @@ import Favicon from "../../favicon"; export default abstract class VectorBasePlatform extends BasePlatform { protected _favicon: Favicon; - async getConfig(): Promise { + public async getConfig(): Promise { return getVectorConfig(); } - getHumanReadableName(): string { + public getHumanReadableName(): string { return 'Vector Base Platform'; // no translation required: only used for analytics } @@ -43,7 +43,7 @@ export default abstract class VectorBasePlatform extends BasePlatform { * it uses canvas, which can trigger a permission prompt in Firefox's resist fingerprinting mode. * See https://github.com/vector-im/element-web/issues/9605. */ - get favicon() { + public get favicon() { if (this._favicon) { return this._favicon; } @@ -62,13 +62,13 @@ export default abstract class VectorBasePlatform extends BasePlatform { this.favicon.badge(notif, { bgColor }); } - setNotificationCount(count: number) { + public setNotificationCount(count: number) { if (this.notificationCount === count) return; super.setNotificationCount(count); this.updateFavicon(); } - setErrorStatus(errorDidOccur: boolean) { + public setErrorStatus(errorDidOccur: boolean) { if (this.errorDidOccur === errorDidOccur) return; super.setErrorStatus(errorDidOccur); this.updateFavicon(); @@ -77,14 +77,14 @@ export default abstract class VectorBasePlatform extends BasePlatform { /** * Begin update polling, if applicable */ - startUpdater() { + public startUpdater() { } /** * Get a sensible default display name for the * device Vector is running on */ - getDefaultDeviceDisplayName(): string { + public getDefaultDeviceDisplayName(): string { return _t("Unknown device"); } } diff --git a/src/vector/platform/WebPlatform.ts b/src/vector/platform/WebPlatform.ts index 4f57908782..bef9c51d30 100644 --- a/src/vector/platform/WebPlatform.ts +++ b/src/vector/platform/WebPlatform.ts @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { UpdateCheckStatus } from "matrix-react-sdk/src/BasePlatform"; +import { UpdateCheckStatus, UpdateStatus } from "matrix-react-sdk/src/BasePlatform"; import request from 'browser-request'; import dis from 'matrix-react-sdk/src/dispatcher/dispatcher'; import { _t } from 'matrix-react-sdk/src/languageHandler'; @@ -31,6 +31,15 @@ import { parseQs } from "../url_utils"; const POKE_RATE_MS = 10 * 60 * 1000; // 10 min +function getNormalizedAppVersion(version: string): string { + // if version looks like semver with leading v, strip it (matches scripts/normalize-version.sh) + const semVerRegex = /^v\d+.\d+.\d+(-.+)?$/; + if (semVerRegex.test(version)) { + return version.substring(1); + } + return version; +} + export default class WebPlatform extends VectorBasePlatform { constructor() { super(); @@ -40,7 +49,7 @@ export default class WebPlatform extends VectorBasePlatform { } } - getHumanReadableName(): string { + public getHumanReadableName(): string { return 'Web Platform'; // no translation required: only used for analytics } @@ -48,7 +57,7 @@ export default class WebPlatform extends VectorBasePlatform { * Returns true if the platform supports displaying * notifications, otherwise false. */ - supportsNotifications(): boolean { + public supportsNotifications(): boolean { return Boolean(window.Notification); } @@ -56,7 +65,7 @@ export default class WebPlatform extends VectorBasePlatform { * Returns true if the application currently has permission * to display notifications. Otherwise false. */ - maySendNotifications(): boolean { + public maySendNotifications(): boolean { return window.Notification.permission === 'granted'; } @@ -67,7 +76,7 @@ export default class WebPlatform extends VectorBasePlatform { * that is 'granted' if the user allowed the request or * 'denied' otherwise. */ - requestNotificationPermission(): Promise { + public requestNotificationPermission(): Promise { // annoyingly, the latest spec says this returns a // promise, but this is only supported in Chrome 46 // and Firefox 47, so adapt the callback API. @@ -99,26 +108,17 @@ export default class WebPlatform extends VectorBasePlatform { return; } - resolve(this.getNormalizedAppVersion(body.trim())); + resolve(getNormalizedAppVersion(body.trim())); }, ); }); } - getNormalizedAppVersion(version: string): string { - // if version looks like semver with leading v, strip it (matches scripts/normalize-version.sh) - const semVerRegex = /^v\d+.\d+.\d+(-.+)?$/; - if (semVerRegex.test(version)) { - return version.substring(1); - } - return version; + public getAppVersion(): Promise { + return Promise.resolve(getNormalizedAppVersion(process.env.VERSION)); } - getAppVersion(): Promise { - return Promise.resolve(this.getNormalizedAppVersion(process.env.VERSION)); - } - - startUpdater() { + public startUpdater(): void { // Poll for an update immediately, and reload the page now if we're out of date // already as we've just initialised an old version of the app somehow. // @@ -127,7 +127,7 @@ export default class WebPlatform extends VectorBasePlatform { // // Ideally, loading an old copy would be impossible with the // cache-control: nocache HTTP header set, but Firefox doesn't always obey it :/ - console.log("startUpdater, current version is " + this.getNormalizedAppVersion(process.env.VERSION)); + console.log("startUpdater, current version is " + getNormalizedAppVersion(process.env.VERSION)); this.pollForUpdate((version: string, newVersion: string) => { const query = parseQs(location); if (query.updated) { @@ -147,16 +147,16 @@ export default class WebPlatform extends VectorBasePlatform { setInterval(() => this.pollForUpdate(showUpdateToast, hideUpdateToast), POKE_RATE_MS); } - async canSelfUpdate(): Promise { + public async canSelfUpdate(): Promise { return true; } - pollForUpdate = ( + private pollForUpdate = ( showUpdate: (currentVersion: string, mostRecentVersion: string) => void, showNoUpdate?: () => void, - ) => { + ): Promise => { return this.getMostRecentVersion().then((mostRecentVersion) => { - const currentVersion = this.getNormalizedAppVersion(process.env.VERSION); + const currentVersion = getNormalizedAppVersion(process.env.VERSION); if (currentVersion !== mostRecentVersion) { if (this.shouldShowUpdate(mostRecentVersion)) { @@ -181,7 +181,7 @@ export default class WebPlatform extends VectorBasePlatform { }); }; - startUpdateCheck() { + public startUpdateCheck(): void { super.startUpdateCheck(); this.pollForUpdate(showUpdateToast, hideUpdateToast).then((updateState) => { dis.dispatch({ @@ -191,11 +191,11 @@ export default class WebPlatform extends VectorBasePlatform { }); } - installUpdate() { + public installUpdate(): void { window.location.reload(); } - getDefaultDeviceDisplayName(): string { + public getDefaultDeviceDisplayName(): string { // strip query-string and fragment from uri const url = new URL(window.location.href); @@ -217,15 +217,7 @@ export default class WebPlatform extends VectorBasePlatform { }); } - screenCaptureErrorString(): string | null { - // it won't work at all if you're not on HTTPS so whine whine whine - if (window.location.protocol !== "https:") { - return _t("You need to be using HTTPS to place a screen-sharing call."); - } - return null; - } - - reload() { + public reload(): void { window.location.reload(); } }