mirror of
https://github.com/vector-im/element-web.git
synced 2024-09-10 07:05:14 +00:00
Abstract electron settings properly to avoid boilerplate-hell (#22491)
* Remove unused method `BasePlatform::screenCaptureErrorString` * Extract SeshatIndexManager into its own file * Improve platform typescripting * Consolidate IPC call promisification into IPCManager * Abstract electron settings properly to avoid boilerplate-hell * i18n * Iterate PR
This commit is contained in:
parent
867fc30ebf
commit
2c0965c240
7 changed files with 283 additions and 311 deletions
|
@ -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",
|
||||
|
|
|
@ -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<number, { resolve, reject }> = {};
|
||||
private nextIpcCallId = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
electron.on('seshatReply', this.onIpcReply);
|
||||
}
|
||||
|
||||
private async ipcCall(name: string, ...args: any[]): Promise<any> {
|
||||
// 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<boolean> {
|
||||
return this.ipcCall('supportsEventIndexing');
|
||||
}
|
||||
|
||||
async initEventIndex(userId: string, deviceId: string): Promise<void> {
|
||||
return this.ipcCall('initEventIndex', userId, deviceId);
|
||||
}
|
||||
|
||||
async addEventToIndex(ev: IMatrixEvent, profile: IMatrixProfile): Promise<void> {
|
||||
return this.ipcCall('addEventToIndex', ev, profile);
|
||||
}
|
||||
|
||||
async deleteEvent(eventId: string): Promise<boolean> {
|
||||
return this.ipcCall('deleteEvent', eventId);
|
||||
}
|
||||
|
||||
async isEventIndexEmpty(): Promise<boolean> {
|
||||
return this.ipcCall('isEventIndexEmpty');
|
||||
}
|
||||
|
||||
async isRoomIndexed(roomId: string): Promise<boolean> {
|
||||
return this.ipcCall('isRoomIndexed', roomId);
|
||||
}
|
||||
|
||||
async commitLiveEvents(): Promise<void> {
|
||||
return this.ipcCall('commitLiveEvents');
|
||||
}
|
||||
|
||||
async searchEventIndex(searchConfig: ISearchArgs): Promise<IResultRoomEvents> {
|
||||
return this.ipcCall('searchEventIndex', searchConfig);
|
||||
}
|
||||
|
||||
async addHistoricEvents(
|
||||
events: IEventAndProfile[],
|
||||
checkpoint: ICrawlerCheckpoint | null,
|
||||
oldCheckpoint: ICrawlerCheckpoint | null,
|
||||
): Promise<boolean> {
|
||||
return this.ipcCall('addHistoricEvents', events, checkpoint, oldCheckpoint);
|
||||
}
|
||||
|
||||
async addCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise<void> {
|
||||
return this.ipcCall('addCrawlerCheckpoint', checkpoint);
|
||||
}
|
||||
|
||||
async removeCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise<void> {
|
||||
return this.ipcCall('removeCrawlerCheckpoint', checkpoint);
|
||||
}
|
||||
|
||||
async loadFileEvents(args): Promise<IEventAndProfile[]> {
|
||||
return this.ipcCall('loadFileEvents', args);
|
||||
}
|
||||
|
||||
async loadCheckpoints(): Promise<ICrawlerCheckpoint[]> {
|
||||
return this.ipcCall('loadCheckpoints');
|
||||
}
|
||||
|
||||
async closeEventIndex(): Promise<void> {
|
||||
return this.ipcCall('closeEventIndex');
|
||||
}
|
||||
|
||||
async getStats(): Promise<IIndexStats> {
|
||||
return this.ipcCall('getStats');
|
||||
}
|
||||
|
||||
async getUserVersion(): Promise<number> {
|
||||
return this.ipcCall('getUserVersion');
|
||||
}
|
||||
|
||||
async setUserVersion(version: number): Promise<void> {
|
||||
return this.ipcCall('setUserVersion', version);
|
||||
}
|
||||
|
||||
async deleteEventIndex(): Promise<void> {
|
||||
return this.ipcCall('deleteEventIndex');
|
||||
}
|
||||
}
|
||||
|
||||
export default class ElectronPlatform extends VectorBasePlatform {
|
||||
private eventIndexManager: BaseEventIndexManager = new SeshatIndexManager();
|
||||
private pendingIpcCalls: Record<number, { resolve, reject }> = {};
|
||||
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<IConfigOptions> {
|
||||
return this.ipcCall('getConfig');
|
||||
public async getConfig(): Promise<IConfigOptions> {
|
||||
return this.ipc.call('getConfig');
|
||||
}
|
||||
|
||||
onUpdateDownloaded = async (ev, { releaseNotes, releaseName }) => {
|
||||
private onUpdateDownloaded = async (ev, { releaseNotes, releaseName }) => {
|
||||
dis.dispatch<CheckUpdatesPayload>({
|
||||
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<string> {
|
||||
return this.ipcCall('getAppVersion');
|
||||
public async getAppVersion(): Promise<string> {
|
||||
return this.ipc.call('getAppVersion');
|
||||
}
|
||||
|
||||
supportsAutoLaunch(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getAutoLaunchEnabled(): Promise<boolean> {
|
||||
return this.ipcCall('getAutoLaunchEnabled');
|
||||
}
|
||||
|
||||
async setAutoLaunchEnabled(enabled: boolean): Promise<void> {
|
||||
return this.ipcCall('setAutoLaunchEnabled', enabled);
|
||||
}
|
||||
|
||||
supportsWarnBeforeExit(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async shouldWarnBeforeExit(): Promise<boolean> {
|
||||
return this.ipcCall('shouldWarnBeforeExit');
|
||||
}
|
||||
|
||||
async setWarnBeforeExit(enabled: boolean): Promise<void> {
|
||||
return this.ipcCall('setWarnBeforeExit', enabled);
|
||||
}
|
||||
|
||||
supportsAutoHideMenuBar(): boolean {
|
||||
// This is irelevant on Mac as Menu bars don't live in the app window
|
||||
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;
|
||||
}
|
||||
|
||||
async getAutoHideMenuBarEnabled(): Promise<boolean> {
|
||||
return this.ipcCall('getAutoHideMenuBarEnabled');
|
||||
}
|
||||
|
||||
async setAutoHideMenuBarEnabled(enabled: boolean): Promise<void> {
|
||||
return this.ipcCall('setAutoHideMenuBarEnabled', enabled);
|
||||
}
|
||||
|
||||
supportsMinimizeToTray(): boolean {
|
||||
// Things other than Mac support tray icons
|
||||
return !isMac;
|
||||
}
|
||||
|
||||
async getMinimizeToTrayEnabled(): Promise<boolean> {
|
||||
return this.ipcCall('getMinimizeToTrayEnabled');
|
||||
}
|
||||
|
||||
async setMinimizeToTrayEnabled(enabled: boolean): Promise<void> {
|
||||
return this.ipcCall('setMinimizeToTrayEnabled', enabled);
|
||||
}
|
||||
|
||||
public supportsTogglingHardwareAcceleration(): boolean {
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getHardwareAccelerationEnabled(): Promise<boolean> {
|
||||
return this.ipcCall('getHardwareAccelerationEnabled');
|
||||
}
|
||||
|
||||
public async setHardwareAccelerationEnabled(enabled: boolean): Promise<void> {
|
||||
return this.ipcCall('setHardwareAccelerationEnabled', enabled);
|
||||
public getSettingValue(settingName: string): Promise<any> {
|
||||
return this.ipc.call("getSettingValue", settingName);
|
||||
}
|
||||
|
||||
public setSettingValue(settingName: string, value: any): Promise<void> {
|
||||
return this.ipc.call("setSettingValue", settingName, value);
|
||||
}
|
||||
|
||||
async canSelfUpdate(): Promise<boolean> {
|
||||
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<string> {
|
||||
public requestNotificationPermission(): Promise<string> {
|
||||
return Promise.resolve('granted');
|
||||
}
|
||||
|
||||
reload() {
|
||||
public reload() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
private async ipcCall(name: string, ...args: any[]): Promise<any> {
|
||||
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<string[]> {
|
||||
return this.ipcCall('getSpellCheckLanguages');
|
||||
public async getSpellCheckLanguages(): Promise<string[]> {
|
||||
return this.ipc.call('getSpellCheckLanguages');
|
||||
}
|
||||
|
||||
async getDesktopCapturerSources(options: GetSourcesOptions): Promise<Array<DesktopCapturerSource>> {
|
||||
return this.ipcCall('getDesktopCapturerSources', options);
|
||||
public async getDesktopCapturerSources(options: GetSourcesOptions): Promise<Array<DesktopCapturerSource>> {
|
||||
return this.ipc.call('getDesktopCapturerSources', options);
|
||||
}
|
||||
|
||||
supportsDesktopCapturer(): boolean {
|
||||
public supportsDesktopCapturer(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getAvailableSpellCheckLanguages(): Promise<string[]> {
|
||||
return this.ipcCall('getAvailableSpellCheckLanguages');
|
||||
public async getAvailableSpellCheckLanguages(): Promise<string[]> {
|
||||
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<string | null> {
|
||||
public async getPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
||||
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<string | null> {
|
||||
public async createPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
||||
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<void> {
|
||||
public async destroyPickleKey(userId: string, deviceId: string): Promise<void> {
|
||||
try {
|
||||
await this.ipcCall('destroyPickleKey', userId, deviceId);
|
||||
await this.ipc.call('destroyPickleKey', userId, deviceId);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
|
70
src/vector/platform/IPCManager.ts
Normal file
70
src/vector/platform/IPCManager.ts
Normal file
|
@ -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<any> } = {};
|
||||
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<any> {
|
||||
// TODO this should be moved into the preload.js file.
|
||||
const ipcCallId = ++this.nextIpcCallId;
|
||||
const deferred = defer<any>();
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
105
src/vector/platform/SeshatIndexManager.ts
Normal file
105
src/vector/platform/SeshatIndexManager.ts
Normal file
|
@ -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<boolean> {
|
||||
return this.ipc.call('supportsEventIndexing');
|
||||
}
|
||||
|
||||
public async initEventIndex(userId: string, deviceId: string): Promise<void> {
|
||||
return this.ipc.call('initEventIndex', userId, deviceId);
|
||||
}
|
||||
|
||||
public async addEventToIndex(ev: IMatrixEvent, profile: IMatrixProfile): Promise<void> {
|
||||
return this.ipc.call('addEventToIndex', ev, profile);
|
||||
}
|
||||
|
||||
public async deleteEvent(eventId: string): Promise<boolean> {
|
||||
return this.ipc.call('deleteEvent', eventId);
|
||||
}
|
||||
|
||||
public async isEventIndexEmpty(): Promise<boolean> {
|
||||
return this.ipc.call('isEventIndexEmpty');
|
||||
}
|
||||
|
||||
public async isRoomIndexed(roomId: string): Promise<boolean> {
|
||||
return this.ipc.call('isRoomIndexed', roomId);
|
||||
}
|
||||
|
||||
public async commitLiveEvents(): Promise<void> {
|
||||
return this.ipc.call('commitLiveEvents');
|
||||
}
|
||||
|
||||
public async searchEventIndex(searchConfig: ISearchArgs): Promise<IResultRoomEvents> {
|
||||
return this.ipc.call('searchEventIndex', searchConfig);
|
||||
}
|
||||
|
||||
public async addHistoricEvents(
|
||||
events: IEventAndProfile[],
|
||||
checkpoint: ICrawlerCheckpoint | null,
|
||||
oldCheckpoint: ICrawlerCheckpoint | null,
|
||||
): Promise<boolean> {
|
||||
return this.ipc.call('addHistoricEvents', events, checkpoint, oldCheckpoint);
|
||||
}
|
||||
|
||||
public async addCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise<void> {
|
||||
return this.ipc.call('addCrawlerCheckpoint', checkpoint);
|
||||
}
|
||||
|
||||
public async removeCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise<void> {
|
||||
return this.ipc.call('removeCrawlerCheckpoint', checkpoint);
|
||||
}
|
||||
|
||||
public async loadFileEvents(args): Promise<IEventAndProfile[]> {
|
||||
return this.ipc.call('loadFileEvents', args);
|
||||
}
|
||||
|
||||
public async loadCheckpoints(): Promise<ICrawlerCheckpoint[]> {
|
||||
return this.ipc.call('loadCheckpoints');
|
||||
}
|
||||
|
||||
public async closeEventIndex(): Promise<void> {
|
||||
return this.ipc.call('closeEventIndex');
|
||||
}
|
||||
|
||||
public async getStats(): Promise<IIndexStats> {
|
||||
return this.ipc.call('getStats');
|
||||
}
|
||||
|
||||
public async getUserVersion(): Promise<number> {
|
||||
return this.ipc.call('getUserVersion');
|
||||
}
|
||||
|
||||
public async setUserVersion(version: number): Promise<void> {
|
||||
return this.ipc.call('setUserVersion', version);
|
||||
}
|
||||
|
||||
public async deleteEventIndex(): Promise<void> {
|
||||
return this.ipc.call('deleteEventIndex');
|
||||
}
|
||||
}
|
|
@ -30,11 +30,11 @@ import Favicon from "../../favicon";
|
|||
export default abstract class VectorBasePlatform extends BasePlatform {
|
||||
protected _favicon: Favicon;
|
||||
|
||||
async getConfig(): Promise<IConfigOptions> {
|
||||
public async getConfig(): Promise<IConfigOptions> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string> {
|
||||
public requestNotificationPermission(): Promise<string> {
|
||||
// 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<string> {
|
||||
return Promise.resolve(getNormalizedAppVersion(process.env.VERSION));
|
||||
}
|
||||
|
||||
getAppVersion(): Promise<string> {
|
||||
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<boolean> {
|
||||
public async canSelfUpdate(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
pollForUpdate = (
|
||||
private pollForUpdate = (
|
||||
showUpdate: (currentVersion: string, mostRecentVersion: string) => void,
|
||||
showNoUpdate?: () => void,
|
||||
) => {
|
||||
): Promise<UpdateStatus> => {
|
||||
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<CheckUpdatesPayload>({
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue