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",
|
"Go to your browser to complete Sign In": "Go to your browser to complete Sign In",
|
||||||
"Unknown device": "Unknown device",
|
"Unknown device": "Unknown device",
|
||||||
"%(appName)s (%(browserName)s, %(osName)s)": "%(appName)s (%(browserName)s, %(osName)s)",
|
"%(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",
|
"Powered by Matrix": "Powered by Matrix",
|
||||||
"Use %(brand)s on mobile": "Use %(brand)s on mobile",
|
"Use %(brand)s on mobile": "Use %(brand)s on mobile",
|
||||||
"Unsupported browser": "Unsupported browser",
|
"Unsupported browser": "Unsupported browser",
|
||||||
|
|
|
@ -18,13 +18,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UpdateCheckStatus } from "matrix-react-sdk/src/BasePlatform";
|
import { UpdateCheckStatus, UpdateStatus } from "matrix-react-sdk/src/BasePlatform";
|
||||||
import BaseEventIndexManager, {
|
import BaseEventIndexManager from 'matrix-react-sdk/src/indexing/BaseEventIndexManager';
|
||||||
ICrawlerCheckpoint,
|
|
||||||
IEventAndProfile,
|
|
||||||
IIndexStats,
|
|
||||||
ISearchArgs,
|
|
||||||
} from 'matrix-react-sdk/src/indexing/BaseEventIndexManager';
|
|
||||||
import dis from 'matrix-react-sdk/src/dispatcher/dispatcher';
|
import dis from 'matrix-react-sdk/src/dispatcher/dispatcher';
|
||||||
import { _t } from 'matrix-react-sdk/src/languageHandler';
|
import { _t } from 'matrix-react-sdk/src/languageHandler';
|
||||||
import SdkConfig from 'matrix-react-sdk/src/SdkConfig';
|
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 { CheckUpdatesPayload } from "matrix-react-sdk/src/dispatcher/payloads/CheckUpdatesPayload";
|
||||||
import ToastStore from "matrix-react-sdk/src/stores/ToastStore";
|
import ToastStore from "matrix-react-sdk/src/stores/ToastStore";
|
||||||
import GenericExpiringToast from "matrix-react-sdk/src/components/views/toasts/GenericExpiringToast";
|
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 { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
import VectorBasePlatform from './VectorBasePlatform';
|
import VectorBasePlatform from './VectorBasePlatform';
|
||||||
|
import { SeshatIndexManager } from "./SeshatIndexManager";
|
||||||
|
import { IPCManager } from "./IPCManager";
|
||||||
|
|
||||||
const electron = window.electron;
|
const electron = window.electron;
|
||||||
const isMac = navigator.platform.toUpperCase().includes('MAC');
|
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
|
// Whitelist payload actions, no point sending most across
|
||||||
if (['call_state'].includes(payload.action)) {
|
if (['call_state'].includes(payload.action)) {
|
||||||
electron.send('app_onAction', payload);
|
electron.send('app_onAction', payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUpdateCheckStatus(status: boolean | string) {
|
function getUpdateCheckStatus(status: boolean | string): UpdateStatus {
|
||||||
if (status === true) {
|
if (status === true) {
|
||||||
return { status: UpdateCheckStatus.Downloading };
|
return { status: UpdateCheckStatus.Downloading };
|
||||||
} else if (status === false) {
|
} 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 {
|
export default class ElectronPlatform extends VectorBasePlatform {
|
||||||
private eventIndexManager: BaseEventIndexManager = new SeshatIndexManager();
|
private readonly ipc = new IPCManager("ipcCall", "ipcReply");
|
||||||
private pendingIpcCalls: Record<number, { resolve, reject }> = {};
|
private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager();
|
||||||
private nextIpcCallId = 0;
|
|
||||||
// this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile
|
// 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() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
dis.register(_onAction);
|
dis.register(onAction);
|
||||||
/*
|
/*
|
||||||
IPC Call `check_updates` returns:
|
IPC Call `check_updates` returns:
|
||||||
true if there is an update available
|
true if there is an update available
|
||||||
|
@ -243,7 +116,6 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||||
rageshake.flush();
|
rageshake.flush();
|
||||||
});
|
});
|
||||||
|
|
||||||
electron.on('ipcReply', this.onIpcReply);
|
|
||||||
electron.on('update-downloaded', this.onUpdateDownloaded);
|
electron.on('update-downloaded', this.onUpdateDownloaded);
|
||||||
|
|
||||||
electron.on('preferences', () => {
|
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> {
|
public async getConfig(): Promise<IConfigOptions> {
|
||||||
return this.ipcCall('getConfig');
|
return this.ipc.call('getConfig');
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateDownloaded = async (ev, { releaseNotes, releaseName }) => {
|
private onUpdateDownloaded = async (ev, { releaseNotes, releaseName }) => {
|
||||||
dis.dispatch<CheckUpdatesPayload>({
|
dis.dispatch<CheckUpdatesPayload>({
|
||||||
action: Action.CheckUpdates,
|
action: Action.CheckUpdates,
|
||||||
status: UpdateCheckStatus.Ready,
|
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
|
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
|
* Return true if platform supports multi-language
|
||||||
* spell-checking, otherwise false.
|
* spell-checking, otherwise false.
|
||||||
*/
|
*/
|
||||||
supportsMultiLanguageSpellCheck(): boolean {
|
public supportsMultiLanguageSpellCheck(): boolean {
|
||||||
// Electron uses OS spell checking on macOS, so no need for in-app options
|
// Electron uses OS spell checking on macOS, so no need for in-app options
|
||||||
if (isMac) return false;
|
if (isMac) return false;
|
||||||
return true;
|
return true;
|
||||||
|
@ -320,15 +192,21 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||||
electron.send('setBadgeCount', count);
|
electron.send('setBadgeCount', count);
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsNotifications(): boolean {
|
public supportsNotifications(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
maySendNotifications(): boolean {
|
public maySendNotifications(): boolean {
|
||||||
return true;
|
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...
|
// GNOME notification spec parses HTML tags for styling...
|
||||||
// Electron Docs state all supported linux notification systems follow this markup spec
|
// 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
|
// 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;
|
const handler = notification.onclick as Function;
|
||||||
notification.onclick = () => {
|
notification.onclick = () => {
|
||||||
handler?.();
|
handler?.();
|
||||||
this.ipcCall('focusWindow');
|
this.ipc.call('focusWindow');
|
||||||
};
|
};
|
||||||
|
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
loudNotification(ev: MatrixEvent, room: Room) {
|
public loudNotification(ev: MatrixEvent, room: Room) {
|
||||||
electron.send('loudNotification');
|
electron.send('loudNotification');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAppVersion(): Promise<string> {
|
public async getAppVersion(): Promise<string> {
|
||||||
return this.ipcCall('getAppVersion');
|
return this.ipc.call('getAppVersion');
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsAutoLaunch(): boolean {
|
public supportsSetting(settingName?: string): boolean {
|
||||||
return true;
|
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<boolean> {
|
public getSettingValue(settingName: string): Promise<any> {
|
||||||
return this.ipcCall('getAutoLaunchEnabled');
|
return this.ipc.call("getSettingValue", settingName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAutoLaunchEnabled(enabled: boolean): Promise<void> {
|
public setSettingValue(settingName: string, value: any): Promise<void> {
|
||||||
return this.ipcCall('setAutoLaunchEnabled', enabled);
|
return this.ipc.call("setSettingValue", settingName, value);
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
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 {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getHardwareAccelerationEnabled(): Promise<boolean> {
|
|
||||||
return this.ipcCall('getHardwareAccelerationEnabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
public async setHardwareAccelerationEnabled(enabled: boolean): Promise<void> {
|
|
||||||
return this.ipcCall('setHardwareAccelerationEnabled', enabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async canSelfUpdate(): Promise<boolean> {
|
async canSelfUpdate(): Promise<boolean> {
|
||||||
const feedUrl = await this.ipcCall('getUpdateFeedUrl');
|
const feedUrl = await this.ipc.call('getUpdateFeedUrl');
|
||||||
return Boolean(feedUrl);
|
return Boolean(feedUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
startUpdateCheck() {
|
public startUpdateCheck() {
|
||||||
super.startUpdateCheck();
|
super.startUpdateCheck();
|
||||||
electron.send('check_updates');
|
electron.send('check_updates');
|
||||||
}
|
}
|
||||||
|
|
||||||
installUpdate() {
|
public installUpdate() {
|
||||||
// IPC to the main process to install the update, since quitAndInstall
|
// 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
|
// doesn't fire the before-quit event so the main process needs to know
|
||||||
// it should exit.
|
// it should exit.
|
||||||
electron.send('install_update');
|
electron.send('install_update');
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultDeviceDisplayName(): string {
|
public getDefaultDeviceDisplayName(): string {
|
||||||
const brand = SdkConfig.get().brand;
|
const brand = SdkConfig.get().brand;
|
||||||
return _t('%(brand)s Desktop (%(platformName)s)', {
|
return _t('%(brand)s Desktop (%(platformName)s)', {
|
||||||
brand,
|
brand,
|
||||||
|
@ -451,86 +285,58 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
screenCaptureErrorString(): string | null {
|
public requestNotificationPermission(): Promise<string> {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
requestNotificationPermission(): Promise<string> {
|
|
||||||
return Promise.resolve('granted');
|
return Promise.resolve('granted');
|
||||||
}
|
}
|
||||||
|
|
||||||
reload() {
|
public reload() {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ipcCall(name: string, ...args: any[]): Promise<any> {
|
public getEventIndexingManager(): BaseEventIndexManager | null {
|
||||||
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 {
|
|
||||||
return this.eventIndexManager;
|
return this.eventIndexManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setLanguage(preferredLangs: string[]) {
|
public async setLanguage(preferredLangs: string[]) {
|
||||||
return this.ipcCall('setLanguage', preferredLangs);
|
return this.ipc.call('setLanguage', preferredLangs);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSpellCheckLanguages(preferredLangs: string[]) {
|
public setSpellCheckLanguages(preferredLangs: string[]) {
|
||||||
this.ipcCall('setSpellCheckLanguages', preferredLangs).catch(error => {
|
this.ipc.call('setSpellCheckLanguages', preferredLangs).catch(error => {
|
||||||
logger.log("Failed to send setSpellCheckLanguages IPC to Electron");
|
logger.log("Failed to send setSpellCheckLanguages IPC to Electron");
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSpellCheckLanguages(): Promise<string[]> {
|
public async getSpellCheckLanguages(): Promise<string[]> {
|
||||||
return this.ipcCall('getSpellCheckLanguages');
|
return this.ipc.call('getSpellCheckLanguages');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDesktopCapturerSources(options: GetSourcesOptions): Promise<Array<DesktopCapturerSource>> {
|
public async getDesktopCapturerSources(options: GetSourcesOptions): Promise<Array<DesktopCapturerSource>> {
|
||||||
return this.ipcCall('getDesktopCapturerSources', options);
|
return this.ipc.call('getDesktopCapturerSources', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsDesktopCapturer(): boolean {
|
public supportsDesktopCapturer(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAvailableSpellCheckLanguages(): Promise<string[]> {
|
public async getAvailableSpellCheckLanguages(): Promise<string[]> {
|
||||||
return this.ipcCall('getAvailableSpellCheckLanguages');
|
return this.ipc.call('getAvailableSpellCheckLanguages');
|
||||||
}
|
}
|
||||||
|
|
||||||
getSSOCallbackUrl(fragmentAfterLogin: string): URL {
|
public getSSOCallbackUrl(fragmentAfterLogin: string): URL {
|
||||||
const url = super.getSSOCallbackUrl(fragmentAfterLogin);
|
const url = super.getSSOCallbackUrl(fragmentAfterLogin);
|
||||||
url.protocol = "element";
|
url.protocol = "element";
|
||||||
url.searchParams.set("element-desktop-ssoid", this.ssoID);
|
url.searchParams.set("element-desktop-ssoid", this.ssoID);
|
||||||
return url;
|
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
|
// this will get intercepted by electron-main will-navigate
|
||||||
super.startSingleSignOn(mxClient, loginType, fragmentAfterLogin, idpId);
|
super.startSingleSignOn(mxClient, loginType, fragmentAfterLogin, idpId);
|
||||||
Modal.createTrackedDialog('Electron', 'SSO', InfoDialog, {
|
Modal.createTrackedDialog('Electron', 'SSO', InfoDialog, {
|
||||||
|
@ -540,16 +346,16 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||||
}
|
}
|
||||||
|
|
||||||
public navigateForwardBack(back: boolean): void {
|
public navigateForwardBack(back: boolean): void {
|
||||||
this.ipcCall(back ? "navigateBack" : "navigateForward");
|
this.ipc.call(back ? "navigateBack" : "navigateForward");
|
||||||
}
|
}
|
||||||
|
|
||||||
public overrideBrowserShortcuts(): boolean {
|
public overrideBrowserShortcuts(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
public async getPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
return await this.ipcCall('getPickleKey', userId, deviceId);
|
return await this.ipc.call('getPickleKey', userId, deviceId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// if we can't connect to the password storage, assume there's no
|
// if we can't connect to the password storage, assume there's no
|
||||||
// pickle key
|
// 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 {
|
try {
|
||||||
return await this.ipcCall('createPickleKey', userId, deviceId);
|
return await this.ipc.call('createPickleKey', userId, deviceId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// if we can't connect to the password storage, assume there's no
|
// if we can't connect to the password storage, assume there's no
|
||||||
// pickle key
|
// 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 {
|
try {
|
||||||
await this.ipcCall('destroyPickleKey', userId, deviceId);
|
await this.ipc.call('destroyPickleKey', userId, deviceId);
|
||||||
} catch (e) {}
|
} 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";
|
import WebPlatform from "./WebPlatform";
|
||||||
|
|
||||||
export default class PWAPlatform extends WebPlatform {
|
export default class PWAPlatform extends WebPlatform {
|
||||||
setNotificationCount(count: number) {
|
public setNotificationCount(count: number): void {
|
||||||
if (!navigator.setAppBadge) return super.setNotificationCount(count);
|
if (!navigator.setAppBadge) return super.setNotificationCount(count);
|
||||||
if (this.notificationCount === count) return;
|
if (this.notificationCount === count) return;
|
||||||
this.notificationCount = count;
|
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 {
|
export default abstract class VectorBasePlatform extends BasePlatform {
|
||||||
protected _favicon: Favicon;
|
protected _favicon: Favicon;
|
||||||
|
|
||||||
async getConfig(): Promise<IConfigOptions> {
|
public async getConfig(): Promise<IConfigOptions> {
|
||||||
return getVectorConfig();
|
return getVectorConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
getHumanReadableName(): string {
|
public getHumanReadableName(): string {
|
||||||
return 'Vector Base Platform'; // no translation required: only used for analytics
|
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.
|
* 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.
|
* See https://github.com/vector-im/element-web/issues/9605.
|
||||||
*/
|
*/
|
||||||
get favicon() {
|
public get favicon() {
|
||||||
if (this._favicon) {
|
if (this._favicon) {
|
||||||
return this._favicon;
|
return this._favicon;
|
||||||
}
|
}
|
||||||
|
@ -62,13 +62,13 @@ export default abstract class VectorBasePlatform extends BasePlatform {
|
||||||
this.favicon.badge(notif, { bgColor });
|
this.favicon.badge(notif, { bgColor });
|
||||||
}
|
}
|
||||||
|
|
||||||
setNotificationCount(count: number) {
|
public setNotificationCount(count: number) {
|
||||||
if (this.notificationCount === count) return;
|
if (this.notificationCount === count) return;
|
||||||
super.setNotificationCount(count);
|
super.setNotificationCount(count);
|
||||||
this.updateFavicon();
|
this.updateFavicon();
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrorStatus(errorDidOccur: boolean) {
|
public setErrorStatus(errorDidOccur: boolean) {
|
||||||
if (this.errorDidOccur === errorDidOccur) return;
|
if (this.errorDidOccur === errorDidOccur) return;
|
||||||
super.setErrorStatus(errorDidOccur);
|
super.setErrorStatus(errorDidOccur);
|
||||||
this.updateFavicon();
|
this.updateFavicon();
|
||||||
|
@ -77,14 +77,14 @@ export default abstract class VectorBasePlatform extends BasePlatform {
|
||||||
/**
|
/**
|
||||||
* Begin update polling, if applicable
|
* Begin update polling, if applicable
|
||||||
*/
|
*/
|
||||||
startUpdater() {
|
public startUpdater() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a sensible default display name for the
|
* Get a sensible default display name for the
|
||||||
* device Vector is running on
|
* device Vector is running on
|
||||||
*/
|
*/
|
||||||
getDefaultDeviceDisplayName(): string {
|
public getDefaultDeviceDisplayName(): string {
|
||||||
return _t("Unknown device");
|
return _t("Unknown device");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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 request from 'browser-request';
|
||||||
import dis from 'matrix-react-sdk/src/dispatcher/dispatcher';
|
import dis from 'matrix-react-sdk/src/dispatcher/dispatcher';
|
||||||
import { _t } from 'matrix-react-sdk/src/languageHandler';
|
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
|
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 {
|
export default class WebPlatform extends VectorBasePlatform {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
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
|
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
|
* Returns true if the platform supports displaying
|
||||||
* notifications, otherwise false.
|
* notifications, otherwise false.
|
||||||
*/
|
*/
|
||||||
supportsNotifications(): boolean {
|
public supportsNotifications(): boolean {
|
||||||
return Boolean(window.Notification);
|
return Boolean(window.Notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +65,7 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||||
* Returns true if the application currently has permission
|
* Returns true if the application currently has permission
|
||||||
* to display notifications. Otherwise false.
|
* to display notifications. Otherwise false.
|
||||||
*/
|
*/
|
||||||
maySendNotifications(): boolean {
|
public maySendNotifications(): boolean {
|
||||||
return window.Notification.permission === 'granted';
|
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
|
* that is 'granted' if the user allowed the request or
|
||||||
* 'denied' otherwise.
|
* 'denied' otherwise.
|
||||||
*/
|
*/
|
||||||
requestNotificationPermission(): Promise<string> {
|
public requestNotificationPermission(): Promise<string> {
|
||||||
// annoyingly, the latest spec says this returns a
|
// annoyingly, the latest spec says this returns a
|
||||||
// promise, but this is only supported in Chrome 46
|
// promise, but this is only supported in Chrome 46
|
||||||
// and Firefox 47, so adapt the callback API.
|
// and Firefox 47, so adapt the callback API.
|
||||||
|
@ -99,26 +108,17 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(this.getNormalizedAppVersion(body.trim()));
|
resolve(getNormalizedAppVersion(body.trim()));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getNormalizedAppVersion(version: string): string {
|
public getAppVersion(): Promise<string> {
|
||||||
// if version looks like semver with leading v, strip it (matches scripts/normalize-version.sh)
|
return Promise.resolve(getNormalizedAppVersion(process.env.VERSION));
|
||||||
const semVerRegex = /^v\d+.\d+.\d+(-.+)?$/;
|
|
||||||
if (semVerRegex.test(version)) {
|
|
||||||
return version.substring(1);
|
|
||||||
}
|
|
||||||
return version;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAppVersion(): Promise<string> {
|
public startUpdater(): void {
|
||||||
return Promise.resolve(this.getNormalizedAppVersion(process.env.VERSION));
|
|
||||||
}
|
|
||||||
|
|
||||||
startUpdater() {
|
|
||||||
// Poll for an update immediately, and reload the page now if we're out of date
|
// 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.
|
// 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
|
// Ideally, loading an old copy would be impossible with the
|
||||||
// cache-control: nocache HTTP header set, but Firefox doesn't always obey it :/
|
// 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) => {
|
this.pollForUpdate((version: string, newVersion: string) => {
|
||||||
const query = parseQs(location);
|
const query = parseQs(location);
|
||||||
if (query.updated) {
|
if (query.updated) {
|
||||||
|
@ -147,16 +147,16 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||||
setInterval(() => this.pollForUpdate(showUpdateToast, hideUpdateToast), POKE_RATE_MS);
|
setInterval(() => this.pollForUpdate(showUpdateToast, hideUpdateToast), POKE_RATE_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
async canSelfUpdate(): Promise<boolean> {
|
public async canSelfUpdate(): Promise<boolean> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pollForUpdate = (
|
private pollForUpdate = (
|
||||||
showUpdate: (currentVersion: string, mostRecentVersion: string) => void,
|
showUpdate: (currentVersion: string, mostRecentVersion: string) => void,
|
||||||
showNoUpdate?: () => void,
|
showNoUpdate?: () => void,
|
||||||
) => {
|
): Promise<UpdateStatus> => {
|
||||||
return this.getMostRecentVersion().then((mostRecentVersion) => {
|
return this.getMostRecentVersion().then((mostRecentVersion) => {
|
||||||
const currentVersion = this.getNormalizedAppVersion(process.env.VERSION);
|
const currentVersion = getNormalizedAppVersion(process.env.VERSION);
|
||||||
|
|
||||||
if (currentVersion !== mostRecentVersion) {
|
if (currentVersion !== mostRecentVersion) {
|
||||||
if (this.shouldShowUpdate(mostRecentVersion)) {
|
if (this.shouldShowUpdate(mostRecentVersion)) {
|
||||||
|
@ -181,7 +181,7 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
startUpdateCheck() {
|
public startUpdateCheck(): void {
|
||||||
super.startUpdateCheck();
|
super.startUpdateCheck();
|
||||||
this.pollForUpdate(showUpdateToast, hideUpdateToast).then((updateState) => {
|
this.pollForUpdate(showUpdateToast, hideUpdateToast).then((updateState) => {
|
||||||
dis.dispatch<CheckUpdatesPayload>({
|
dis.dispatch<CheckUpdatesPayload>({
|
||||||
|
@ -191,11 +191,11 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
installUpdate() {
|
public installUpdate(): void {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultDeviceDisplayName(): string {
|
public getDefaultDeviceDisplayName(): string {
|
||||||
// strip query-string and fragment from uri
|
// strip query-string and fragment from uri
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
|
|
||||||
|
@ -217,15 +217,7 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
screenCaptureErrorString(): string | null {
|
public reload(): void {
|
||||||
// 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() {
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue