Merge pull request #13756 from vector-im/t3chguy/toasts2

Convert platforms to Typescript
pull/13806/head
Michael Telatynski 2020-05-22 16:35:57 +01:00 committed by GitHub
commit b4284b4c68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 110 additions and 118 deletions

View File

@ -32,3 +32,10 @@ declare global {
InstallTrigger: any;
}
}
// add method which is missing from the node typing
declare module "url" {
interface Url {
format(): string;
}
}

View File

@ -1,5 +1,3 @@
// @flow
/*
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
@ -21,11 +19,20 @@ limitations under the License.
*/
import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform';
import BaseEventIndexManager from 'matrix-react-sdk/src/indexing/BaseEventIndexManager';
import BaseEventIndexManager, {
MatrixEvent,
MatrixProfile,
SearchResult,
CrawlerCheckpoint,
EventAndProfile,
SearchArgs,
IndexStats
} from 'matrix-react-sdk/src/indexing/BaseEventIndexManager';
import dis from 'matrix-react-sdk/src/dispatcher/dispatcher';
import { _t, _td } from 'matrix-react-sdk/src/languageHandler';
import * as rageshake from 'matrix-react-sdk/src/rageshake/rageshake';
import {MatrixClient} from "matrix-js-sdk";
import {MatrixClient} from "matrix-js-sdk/src/client";
import {Room} from "matrix-js-sdk/src/models/room";
import Modal from "matrix-react-sdk/src/Modal";
import InfoDialog from "matrix-react-sdk/src/components/views/dialogs/InfoDialog";
import Spinner from "matrix-react-sdk/src/components/views/elements/Spinner";
@ -34,6 +41,7 @@ import {Key} from "matrix-react-sdk/src/Keyboard";
import React from "react";
import {randomString} from "matrix-js-sdk/src/randomstring";
import {Action} from "matrix-react-sdk/src/dispatcher/actions";
import { ActionPayload } from "matrix-react-sdk/src/dispatcher/payloads";
const ipcRenderer = window.ipcRenderer;
const isMac = navigator.platform.toUpperCase().includes('MAC');
@ -57,7 +65,7 @@ function platformFriendlyName(): string {
}
}
function _onAction(payload: Object) {
function _onAction(payload: ActionPayload) {
// Whitelist payload actions, no point sending most across
if (['call_state'].includes(payload.action)) {
ipcRenderer.send('app_onAction', payload);
@ -77,53 +85,60 @@ function getUpdateCheckStatus(status) {
}
}
interface IPCPayload {
id?: number;
error?: string;
reply?: any;
}
class SeshatIndexManager extends BaseEventIndexManager {
private pendingIpcCalls: Record<number, { resolve, reject }> = {};
private nextIpcCallId: number = 0;
constructor() {
super();
this._pendingIpcCalls = {};
this._nextIpcCallId = 0;
ipcRenderer.on('seshatReply', this._onIpcReply.bind(this));
ipcRenderer.on('seshatReply', this._onIpcReply);
}
async _ipcCall(name: string, ...args: []): Promise<{}> {
async _ipcCall(name: string, ...args: any[]): Promise<any> {
// TODO this should be moved into the preload.js file.
const ipcCallId = ++this._nextIpcCallId;
const ipcCallId = ++this.nextIpcCallId;
return new Promise((resolve, reject) => {
this._pendingIpcCalls[ipcCallId] = {resolve, reject};
this.pendingIpcCalls[ipcCallId] = {resolve, reject};
window.ipcRenderer.send('seshat', {id: ipcCallId, name, args});
});
}
_onIpcReply(ev: {}, payload: {}) {
_onIpcReply = (ev: {}, payload: IPCPayload) => {
if (payload.id === undefined) {
console.warn("Ignoring IPC reply with no ID");
return;
}
if (this._pendingIpcCalls[payload.id] === undefined) {
if (this.pendingIpcCalls[payload.id] === undefined) {
console.warn("Unknown IPC payload ID: " + payload.id);
return;
}
const callbacks = this._pendingIpcCalls[payload.id];
delete this._pendingIpcCalls[payload.id];
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(): Promise<> {
async initEventIndex(): Promise<void> {
return this._ipcCall('initEventIndex');
}
async addEventToIndex(ev: MatrixEvent, profile: MatrixProfile): Promise<> {
async addEventToIndex(ev: MatrixEvent, profile: MatrixProfile): Promise<void> {
return this._ipcCall('addEventToIndex', ev, profile);
}
@ -135,27 +150,27 @@ class SeshatIndexManager extends BaseEventIndexManager {
return this._ipcCall('isEventIndexEmpty');
}
async commitLiveEvents(): Promise<> {
async commitLiveEvents(): Promise<void> {
return this._ipcCall('commitLiveEvents');
}
async searchEventIndex(searchConfig: SearchConfig): Promise<SearchResult> {
async searchEventIndex(searchConfig: SearchArgs): Promise<SearchResult> {
return this._ipcCall('searchEventIndex', searchConfig);
}
async addHistoricEvents(
events: [HistoricEvent],
events: [EventAndProfile],
checkpoint: CrawlerCheckpoint | null,
oldCheckpoint: CrawlerCheckpoint | null,
): Promise<> {
): Promise<boolean> {
return this._ipcCall('addHistoricEvents', events, checkpoint, oldCheckpoint);
}
async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<void> {
return this._ipcCall('addCrawlerCheckpoint', checkpoint);
}
async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<void> {
return this._ipcCall('removeCrawlerCheckpoint', checkpoint);
}
@ -167,27 +182,29 @@ class SeshatIndexManager extends BaseEventIndexManager {
return this._ipcCall('loadCheckpoints');
}
async closeEventIndex(): Promise<> {
async closeEventIndex(): Promise<void> {
return this._ipcCall('closeEventIndex');
}
async getStats(): Promise<> {
async getStats(): Promise<IndexStats> {
return this._ipcCall('getStats');
}
async deleteEventIndex(): Promise<> {
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: number = 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
private ssoID: string = randomString(32);
constructor() {
super();
this._pendingIpcCalls = {};
this._nextIpcCallId = 0;
this.eventIndexManager = new SeshatIndexManager();
dis.register(_onAction);
/*
IPC Call `check_updates` returns:
@ -217,9 +234,6 @@ export default class ElectronPlatform extends VectorBasePlatform {
dis.fire(Action.ViewUserSettings);
});
this.startUpdateCheck = this.startUpdateCheck.bind(this);
this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
// register OS-specific shortcuts
if (isMac) {
registerShortcut(Categories.NAVIGATION, {
@ -253,8 +267,6 @@ export default class ElectronPlatform extends VectorBasePlatform {
});
}
// 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.ssoID = randomString(32);
this._ipcCall("startSSOFlow", this.ssoID);
}
@ -290,7 +302,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
return true;
}
displayNotification(title: string, msg: string, avatarUrl: string, room: Object): Notification {
displayNotification(title: string, msg: string, avatarUrl: string, room: Room): 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
@ -307,14 +319,14 @@ export default class ElectronPlatform extends VectorBasePlatform {
silent: true, // we play our own sounds
};
if (avatarUrl) notifBody['icon'] = avatarUrl;
const notification = new global.Notification(title, notifBody);
const notification = new window.Notification(title, notifBody);
notification.onclick = () => {
dis.dispatch({
action: 'view_room',
room_id: room.roomId,
});
global.focus();
window.focus();
this._ipcCall('focusWindow');
};
@ -337,11 +349,11 @@ export default class ElectronPlatform extends VectorBasePlatform {
return true;
}
async getAutoLaunchEnabled(): boolean {
async getAutoLaunchEnabled(): Promise<boolean> {
return this._ipcCall('getAutoLaunchEnabled');
}
async setAutoLaunchEnabled(enabled: boolean): void {
async setAutoLaunchEnabled(enabled: boolean): Promise<void> {
return this._ipcCall('setAutoLaunchEnabled', enabled);
}
@ -350,11 +362,11 @@ export default class ElectronPlatform extends VectorBasePlatform {
return !isMac;
}
async getAutoHideMenuBarEnabled(): boolean {
async getAutoHideMenuBarEnabled(): Promise<boolean> {
return this._ipcCall('getAutoHideMenuBarEnabled');
}
async setAutoHideMenuBarEnabled(enabled: boolean): void {
async setAutoHideMenuBarEnabled(enabled: boolean): Promise<void> {
return this._ipcCall('setAutoHideMenuBarEnabled', enabled);
}
@ -363,25 +375,25 @@ export default class ElectronPlatform extends VectorBasePlatform {
return !isMac;
}
async getMinimizeToTrayEnabled(): boolean {
async getMinimizeToTrayEnabled(): Promise<boolean> {
return this._ipcCall('getMinimizeToTrayEnabled');
}
async setMinimizeToTrayEnabled(enabled: boolean): void {
async setMinimizeToTrayEnabled(enabled: boolean): Promise<void> {
return this._ipcCall('setMinimizeToTrayEnabled', enabled);
}
async canSelfUpdate(): boolean {
async canSelfUpdate(): Promise<boolean> {
const feedUrl = await this._ipcCall('getUpdateFeedUrl');
return Boolean(feedUrl);
}
startUpdateCheck() {
startUpdateCheck = () => {
if (this.showUpdateCheck) return;
super.startUpdateCheck();
ipcRenderer.send('check_updates');
}
};
installUpdate() {
// IPC to the main process to install the update, since quitAndInstall
@ -394,7 +406,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
return _t('Riot Desktop (%(platformName)s)', { platformName: platformFriendlyName() });
}
screenCaptureErrorString(): ?string {
screenCaptureErrorString(): string | null {
return null;
}
@ -409,10 +421,10 @@ export default class ElectronPlatform extends VectorBasePlatform {
window.location.reload(false);
}
async _ipcCall(name, ...args) {
const ipcCallId = ++this._nextIpcCallId;
async _ipcCall(name: string, ...args: any[]): Promise<any> {
const ipcCallId = ++this.nextIpcCallId;
return new Promise((resolve, reject) => {
this._pendingIpcCalls[ipcCallId] = {resolve, reject};
this.pendingIpcCalls[ipcCallId] = {resolve, reject};
window.ipcRenderer.send('ipcCall', {id: ipcCallId, name, args});
// Maybe add a timeout to these? Probably not necessary.
});
@ -424,13 +436,13 @@ export default class ElectronPlatform extends VectorBasePlatform {
return;
}
if (this._pendingIpcCalls[payload.id] === undefined) {
if (this.pendingIpcCalls[payload.id] === undefined) {
console.warn("Unknown IPC payload ID: " + payload.id);
return;
}
const callbacks = this._pendingIpcCalls[payload.id];
delete this._pendingIpcCalls[payload.id];
const callbacks = this.pendingIpcCalls[payload.id];
delete this.pendingIpcCalls[payload.id];
if (payload.error) {
callbacks.reject(payload.error);
} else {

View File

@ -1,9 +1,7 @@
// @flow
/*
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2018, 2020 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
@ -24,7 +22,7 @@ import { _t } from 'matrix-react-sdk/src/languageHandler';
import dis from 'matrix-react-sdk/src/dispatcher/dispatcher';
import {getVectorConfig} from "../getconfig";
import Favico from '../../favicon';
import Favicon from "../../favicon";
export const updateCheckStatusEnum = {
CHECKING: 'CHECKING',
@ -37,14 +35,9 @@ export const updateCheckStatusEnum = {
/**
* Vector-specific extensions to the BasePlatform template
*/
export default class VectorBasePlatform extends BasePlatform {
constructor() {
super();
this.showUpdateCheck = false;
this.startUpdateCheck = this.startUpdateCheck.bind(this);
this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
}
export default abstract class VectorBasePlatform extends BasePlatform {
protected showUpdateCheck: boolean = false;
protected _favicon: Favicon;
async getConfig(): Promise<{}> {
return getVectorConfig();
@ -55,40 +48,27 @@ export default class VectorBasePlatform extends BasePlatform {
}
/**
* Delay creating the `Favico` instance until first use (on the first notification) as
* it uses canvas, which can trigger a permission prompt in Firefox's resist
* fingerprinting mode.
* Delay creating the `Favicon` instance until first use (on the first notification) as
* it uses canvas, which can trigger a permission prompt in Firefox's resist fingerprinting mode.
* See https://github.com/vector-im/riot-web/issues/9605.
*/
get favicon() {
if (this._favicon) {
return this._favicon;
}
// The 'animations' are really low framerate and look terrible.
// Also it re-starts the animation every time you set the badge,
// and we set the state each time, even if the value hasn't changed,
// so we'd need to fix that if enabling the animation.
this._favicon = new Favico({ animation: 'none' });
return this._favicon;
return this._favicon = new Favicon();
}
_updateFavicon() {
try {
// This needs to be in in a try block as it will throw
// if there are more than 100 badge count changes in
// its internal queue
let bgColor = "#d00";
let notif = this.notificationCount;
let bgColor = "#d00";
let notif: string | number = this.notificationCount;
if (this.errorDidOccur) {
notif = notif || "×";
bgColor = "#f00";
}
this.favicon.badge(notif, { bgColor });
} catch (e) {
console.warn(`Failed to set badge count: ${e.message}`);
if (this.errorDidOccur) {
notif = notif || "×";
bgColor = "#f00";
}
this.favicon.badge(notif, { bgColor });
}
setNotificationCount(count: number) {
@ -112,25 +92,25 @@ export default class VectorBasePlatform extends BasePlatform {
/**
* Whether we can call checkForUpdate on this platform build
*/
async canSelfUpdate(): boolean {
async canSelfUpdate(): Promise<boolean> {
return false;
}
startUpdateCheck() {
startUpdateCheck = () => {
this.showUpdateCheck = true;
dis.dispatch({
action: 'check_updates',
value: { status: updateCheckStatusEnum.CHECKING },
});
}
};
stopUpdateCheck() {
stopUpdateCheck = () => {
this.showUpdateCheck = false;
dis.dispatch({
action: 'check_updates',
value: false,
});
}
};
getUpdateCheckStatusEnum() {
return updateCheckStatusEnum;

View File

@ -1,5 +1,3 @@
// @flow
/*
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
@ -22,6 +20,7 @@ import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform';
import request from 'browser-request';
import dis from 'matrix-react-sdk/src/dispatcher/dispatcher';
import { _t } from 'matrix-react-sdk/src/languageHandler';
import {Room} from "matrix-js-sdk/src/models/room";
import url from 'url';
import UAParser from 'ua-parser-js';
@ -29,13 +28,7 @@ import UAParser from 'ua-parser-js';
const POKE_RATE_MS = 10 * 60 * 1000; // 10 min
export default class WebPlatform extends VectorBasePlatform {
constructor() {
super();
this.runningVersion = null;
this.startUpdateCheck = this.startUpdateCheck.bind(this);
this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
}
private runningVersion: string = null;
getHumanReadableName(): string {
return 'Web Platform'; // no translation required: only used for analytics
@ -46,7 +39,7 @@ export default class WebPlatform extends VectorBasePlatform {
* notifications, otherwise false.
*/
supportsNotifications(): boolean {
return Boolean(global.Notification);
return Boolean(window.Notification);
}
/**
@ -54,7 +47,7 @@ export default class WebPlatform extends VectorBasePlatform {
* to display notifications. Otherwise false.
*/
maySendNotifications(): boolean {
return global.Notification.permission === 'granted';
return window.Notification.permission === 'granted';
}
/**
@ -69,27 +62,27 @@ export default class WebPlatform extends VectorBasePlatform {
// promise, but this is only supported in Chrome 46
// and Firefox 47, so adapt the callback API.
return new Promise(function(resolve, reject) {
global.Notification.requestPermission((result) => {
window.Notification.requestPermission((result) => {
resolve(result);
});
});
}
displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
displayNotification(title: string, msg: string, avatarUrl: string, room: Room) {
const notifBody = {
body: msg,
tag: "vector",
silent: true, // we play our own sounds
};
if (avatarUrl) notifBody['icon'] = avatarUrl;
const notification = new global.Notification(title, notifBody);
const notification = new window.Notification(title, notifBody);
notification.onclick = function() {
dis.dispatch({
action: 'view_room',
room_id: room.roomId,
});
global.focus();
window.focus();
notification.close();
};
}
@ -131,14 +124,14 @@ export default class WebPlatform extends VectorBasePlatform {
startUpdater() {
this.pollForUpdate();
setInterval(this.pollForUpdate.bind(this), POKE_RATE_MS);
setInterval(this.pollForUpdate, POKE_RATE_MS);
}
async canSelfUpdate(): boolean {
async canSelfUpdate(): Promise<boolean> {
return true;
}
pollForUpdate() {
pollForUpdate = () => {
return this._getVersion().then((ver) => {
if (this.runningVersion === null) {
this.runningVersion = ver;
@ -159,9 +152,9 @@ export default class WebPlatform extends VectorBasePlatform {
detail: err.message || err.status ? err.status.toString() : 'Unknown Error',
};
});
}
};
startUpdateCheck() {
startUpdateCheck = () => {
if (this.showUpdateCheck) return;
super.startUpdateCheck();
this.pollForUpdate().then((updateState) => {
@ -172,7 +165,7 @@ export default class WebPlatform extends VectorBasePlatform {
value: updateState,
});
});
}
};
installUpdate() {
window.location.reload(true);
@ -204,9 +197,9 @@ export default class WebPlatform extends VectorBasePlatform {
});
}
screenCaptureErrorString(): ?string {
screenCaptureErrorString(): string | null {
// it won't work at all if you're not on HTTPS so whine whine whine
if (!global.window || global.window.location.protocol !== "https:") {
if (window.location.protocol !== "https:") {
return _t("You need to be using HTTPS to place a screen-sharing call.");
}
return null;