Merge remote-tracking branch 'upstream/develop' into task/messages-ts

This commit is contained in:
Šimon Brandner 2021-09-27 10:32:25 +02:00
commit 6a88ac900c
No known key found for this signature in database
GPG Key ID: 55C211A1226CB17D
13 changed files with 219 additions and 191 deletions

View File

@ -49,6 +49,8 @@ import PerformanceMonitor from "../performance";
import UIStore from "../stores/UIStore";
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake";
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
/* eslint-disable @typescript-eslint/naming-convention */
@ -92,6 +94,7 @@ declare global {
mxUIStore: UIStore;
mxSetupEncryptionStore?: SetupEncryptionStore;
mxRoomScrollStateStore?: RoomScrollStateStore;
mxActiveWidgetStore?: ActiveWidgetStore;
mxOnRecaptchaLoaded?: () => void;
electron?: Electron;
}
@ -223,6 +226,15 @@ declare global {
) => string;
isReady: () => boolean;
};
// eslint-disable-next-line no-var, camelcase
var mx_rage_logger: ConsoleLogger;
// eslint-disable-next-line no-var, camelcase
var mx_rage_initPromise: Promise<void>;
// eslint-disable-next-line no-var, camelcase
var mx_rage_initStoragePromise: Promise<void>;
// eslint-disable-next-line no-var, camelcase
var mx_rage_store: IndexedDBLogStore;
}
/* eslint-enable @typescript-eslint/naming-convention */

View File

@ -786,7 +786,7 @@ async function startMatrixClient(startSyncing = true): Promise<void> {
UserActivity.sharedInstance().start();
DMRoomMap.makeShared().start();
IntegrationManagers.sharedInstance().startWatching();
ActiveWidgetStore.start();
ActiveWidgetStore.instance.start();
CallHandler.sharedInstance().start();
// Start Mjolnir even though we haven't checked the feature flag yet. Starting
@ -892,7 +892,7 @@ export function stopMatrixClient(unsetClient = true): void {
UserActivity.sharedInstance().stop();
TypingStore.sharedInstance().reset();
Presence.stop();
ActiveWidgetStore.stop();
ActiveWidgetStore.instance.stop();
IntegrationManagers.sharedInstance().stopWatching();
Mjolnir.sharedInstance().stop();
DeviceListener.sharedInstance().stop();

View File

@ -163,7 +163,7 @@ export default class AppTile extends React.Component<IProps, IState> {
if (this.state.hasPermissionToLoad && !hasPermissionToLoad) {
// Force the widget to be non-persistent (able to be deleted/forgotten)
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
PersistedElement.destroyElement(this.persistKey);
if (this.sgWidget) this.sgWidget.stop();
}
@ -198,8 +198,8 @@ export default class AppTile extends React.Component<IProps, IState> {
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
// if it's not remaining on screen, get rid of the PersistedElement container
if (!ActiveWidgetStore.getWidgetPersistence(this.props.app.id)) {
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
if (!ActiveWidgetStore.instance.getWidgetPersistence(this.props.app.id)) {
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
PersistedElement.destroyElement(this.persistKey);
}
@ -282,7 +282,7 @@ export default class AppTile extends React.Component<IProps, IState> {
// Delete the widget from the persisted store for good measure.
PersistedElement.destroyElement(this.persistKey);
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
if (this.sgWidget) this.sgWidget.stop({ forceDestroy: true });
}

View File

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import RoomViewStore from '../../../stores/RoomViewStore';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/ActiveWidgetStore';
import WidgetUtils from '../../../utils/WidgetUtils';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -39,13 +39,13 @@ export default class PersistentApp extends React.Component<{}, IState> {
this.state = {
roomId: RoomViewStore.getRoomId(),
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
persistentWidgetId: ActiveWidgetStore.instance.getPersistentWidgetId(),
};
}
public componentDidMount(): void {
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
ActiveWidgetStore.on('update', this.onActiveWidgetStoreUpdate);
ActiveWidgetStore.instance.on(ActiveWidgetStoreEvent.Update, this.onActiveWidgetStoreUpdate);
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
}
@ -53,7 +53,7 @@ export default class PersistentApp extends React.Component<{}, IState> {
if (this.roomStoreToken) {
this.roomStoreToken.remove();
}
ActiveWidgetStore.removeListener('update', this.onActiveWidgetStoreUpdate);
ActiveWidgetStore.instance.removeListener(ActiveWidgetStoreEvent.Update, this.onActiveWidgetStoreUpdate);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room.myMembership", this.onMyMembership);
}
@ -68,23 +68,23 @@ export default class PersistentApp extends React.Component<{}, IState> {
private onActiveWidgetStoreUpdate = (): void => {
this.setState({
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
persistentWidgetId: ActiveWidgetStore.instance.getPersistentWidgetId(),
});
};
private onMyMembership = async (room: Room, membership: string): Promise<void> => {
const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId);
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId);
if (membership !== "join") {
// we're not in the room anymore - delete
if (room .roomId === persistentWidgetInRoomId) {
ActiveWidgetStore.destroyPersistentWidget(this.state.persistentWidgetId);
ActiveWidgetStore.instance.destroyPersistentWidget(this.state.persistentWidgetId);
}
}
};
public render(): JSX.Element {
if (this.state.persistentWidgetId) {
const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId);
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId);
const persistentWidgetInRoom = MatrixClientPeg.get().getRoom(persistentWidgetInRoomId);
@ -96,7 +96,7 @@ export default class PersistentApp extends React.Component<{}, IState> {
if (this.state.roomId !== persistentWidgetInRoomId && myMembership === "join") {
// get the widget data
const appEvent = WidgetUtils.getRoomWidgets(persistentWidgetInRoom).find((ev) => {
return ev.getStateKey() === ActiveWidgetStore.getPersistentWidgetId();
return ev.getStateKey() === ActiveWidgetStore.instance.getPersistentWidgetId();
});
const app = WidgetUtils.makeAppConfig(
appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(),

View File

@ -46,12 +46,10 @@ const FLUSH_RATE_MS = 30 * 1000;
const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB
// A class which monkey-patches the global console and stores log lines.
class ConsoleLogger {
constructor() {
this.logs = "";
}
export class ConsoleLogger {
private logs = "";
monkeyPatch(consoleObj) {
public monkeyPatch(consoleObj: Console): void {
// Monkey-patch console logging
const consoleFunctionsToLevels = {
log: "I",
@ -69,14 +67,14 @@ class ConsoleLogger {
});
}
log(level, ...args) {
private log(level: string, ...args: (Error | DOMException | object | string)[]): void {
// We don't know what locale the user may be running so use ISO strings
const ts = new Date().toISOString();
// Convert objects and errors to helpful things
args = args.map((arg) => {
if (arg instanceof DOMException) {
return arg.message + ` (${arg.name} | ${arg.code}) ` + (arg.stack ? `\n${arg.stack}` : '');
return arg.message + ` (${arg.name} | ${arg.code})`;
} else if (arg instanceof Error) {
return arg.message + (arg.stack ? `\n${arg.stack}` : '');
} else if (typeof (arg) === 'object') {
@ -118,7 +116,7 @@ class ConsoleLogger {
* @param {boolean} keepLogs True to not delete logs after flushing.
* @return {string} \n delimited log lines to flush.
*/
flush(keepLogs) {
public flush(keepLogs?: boolean): string {
// The ConsoleLogger doesn't care how these end up on disk, it just
// flushes them to the caller.
if (keepLogs) {
@ -131,27 +129,28 @@ class ConsoleLogger {
}
// A class which stores log lines in an IndexedDB instance.
class IndexedDBLogStore {
constructor(indexedDB, logger) {
this.indexedDB = indexedDB;
this.logger = logger;
this.id = "instance-" + Math.random() + Date.now();
this.index = 0;
this.db = null;
export class IndexedDBLogStore {
private id: string;
private index = 0;
private db = null;
private flushPromise = null;
private flushAgainPromise = null;
// these promises are cleared as soon as fulfilled
this.flushPromise = null;
// set if flush() is called whilst one is ongoing
this.flushAgainPromise = null;
constructor(
private indexedDB: IDBFactory,
private logger: ConsoleLogger,
) {
this.id = "instance-" + Math.random() + Date.now();
}
/**
* @return {Promise} Resolves when the store is ready.
*/
connect() {
public connect(): Promise<void> {
const req = this.indexedDB.open("logs");
return new Promise((resolve, reject) => {
req.onsuccess = (event) => {
req.onsuccess = (event: Event) => {
// @ts-ignore
this.db = event.target.result;
// Periodically flush logs to local storage / indexeddb
setInterval(this.flush.bind(this), FLUSH_RATE_MS);
@ -160,6 +159,7 @@ class IndexedDBLogStore {
req.onerror = (event) => {
const err = (
// @ts-ignore
"Failed to open log database: " + event.target.error.name
);
console.error(err);
@ -168,6 +168,7 @@ class IndexedDBLogStore {
// First time: Setup the object store
req.onupgradeneeded = (event) => {
// @ts-ignore
const db = event.target.result;
const logObjStore = db.createObjectStore("logs", {
keyPath: ["id", "index"],
@ -178,7 +179,7 @@ class IndexedDBLogStore {
logObjStore.createIndex("id", "id", { unique: false });
logObjStore.add(
this._generateLogEntry(
this.generateLogEntry(
new Date() + " ::: Log database was created.",
),
);
@ -186,7 +187,7 @@ class IndexedDBLogStore {
const lastModifiedStore = db.createObjectStore("logslastmod", {
keyPath: "id",
});
lastModifiedStore.add(this._generateLastModifiedTime());
lastModifiedStore.add(this.generateLastModifiedTime());
};
});
}
@ -210,7 +211,7 @@ class IndexedDBLogStore {
*
* @return {Promise} Resolved when the logs have been flushed.
*/
flush() {
public flush(): Promise<void> {
// check if a flush() operation is ongoing
if (this.flushPromise) {
if (this.flushAgainPromise) {
@ -227,7 +228,7 @@ class IndexedDBLogStore {
}
// there is no flush promise or there was but it has finished, so do
// a brand new one, destroying the chain which may have been built up.
this.flushPromise = new Promise((resolve, reject) => {
this.flushPromise = new Promise<void>((resolve, reject) => {
if (!this.db) {
// not connected yet or user rejected access for us to r/w to the db.
reject(new Error("No connected database"));
@ -251,9 +252,9 @@ class IndexedDBLogStore {
new Error("Failed to write logs: " + event.target.errorCode),
);
};
objStore.add(this._generateLogEntry(lines));
objStore.add(this.generateLogEntry(lines));
const lastModStore = txn.objectStore("logslastmod");
lastModStore.put(this._generateLastModifiedTime());
lastModStore.put(this.generateLastModifiedTime());
}).then(() => {
this.flushPromise = null;
});
@ -270,12 +271,12 @@ class IndexedDBLogStore {
* log ID). The objects have said log ID in an "id" field and "lines" which
* is a big string with all the new-line delimited logs.
*/
async consume() {
public async consume(): Promise<{lines: string, id: string}[]> {
const db = this.db;
// Returns: a string representing the concatenated logs for this ID.
// Stops adding log fragments when the size exceeds maxSize
function fetchLogs(id, maxSize) {
function fetchLogs(id: string, maxSize: number): Promise<string> {
const objectStore = db.transaction("logs", "readonly").objectStore("logs");
return new Promise((resolve, reject) => {
@ -301,7 +302,7 @@ class IndexedDBLogStore {
}
// Returns: A sorted array of log IDs. (newest first)
function fetchLogIds() {
function fetchLogIds(): Promise<string[]> {
// To gather all the log IDs, query for all records in logslastmod.
const o = db.transaction("logslastmod", "readonly").objectStore(
"logslastmod",
@ -319,8 +320,8 @@ class IndexedDBLogStore {
});
}
function deleteLogs(id) {
return new Promise((resolve, reject) => {
function deleteLogs(id: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
const txn = db.transaction(
["logs", "logslastmod"], "readwrite",
);
@ -389,7 +390,7 @@ class IndexedDBLogStore {
return logs;
}
_generateLogEntry(lines) {
private generateLogEntry(lines: string): {id: string, lines: string, index: number} {
return {
id: this.id,
lines: lines,
@ -397,7 +398,7 @@ class IndexedDBLogStore {
};
}
_generateLastModifiedTime() {
private generateLastModifiedTime(): {id: string, ts: number} {
return {
id: this.id,
ts: Date.now(),
@ -415,15 +416,19 @@ class IndexedDBLogStore {
* @return {Promise<T[]>} Resolves to an array of whatever you returned from
* resultMapper.
*/
function selectQuery(store, keyRange, resultMapper) {
function selectQuery<T>(
store: IDBIndex, keyRange: IDBKeyRange, resultMapper: (cursor: IDBCursorWithValue) => T,
): Promise<T[]> {
const query = store.openCursor(keyRange);
return new Promise((resolve, reject) => {
const results = [];
query.onerror = (event) => {
// @ts-ignore
reject(new Error("Query failed: " + event.target.errorCode));
};
// collect results
query.onsuccess = (event) => {
// @ts-ignore
const cursor = event.target.result;
if (!cursor) {
resolve(results);
@ -442,7 +447,7 @@ function selectQuery(store, keyRange, resultMapper) {
* be set up immediately for the logs.
* @return {Promise} Resolves when set up.
*/
export function init(setUpPersistence = true) {
export function init(setUpPersistence = true): Promise<void> {
if (global.mx_rage_initPromise) {
return global.mx_rage_initPromise;
}
@ -462,7 +467,7 @@ export function init(setUpPersistence = true) {
* then this no-ops.
* @return {Promise} Resolves when complete.
*/
export function tryInitStorage() {
export function tryInitStorage(): Promise<void> {
if (global.mx_rage_initStoragePromise) {
return global.mx_rage_initStoragePromise;
}

View File

@ -1,110 +0,0 @@
/*
Copyright 2018 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 EventEmitter from 'events';
import { MatrixClientPeg } from '../MatrixClientPeg';
import { WidgetMessagingStore } from "./widgets/WidgetMessagingStore";
/**
* Stores information about the widgets active in the app right now:
* * What widget is set to remain always-on-screen, if any
* Only one widget may be 'always on screen' at any one time.
* * Negotiated capabilities for active apps
*/
class ActiveWidgetStore extends EventEmitter {
constructor() {
super();
this._persistentWidgetId = null;
// What room ID each widget is associated with (if it's a room widget)
this._roomIdByWidgetId = {};
this.onRoomStateEvents = this.onRoomStateEvents.bind(this);
this.dispatcherRef = null;
}
start() {
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
}
stop() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
this._roomIdByWidgetId = {};
}
onRoomStateEvents(ev, state) {
// XXX: This listens for state events in order to remove the active widget.
// Everything else relies on views listening for events and calling setters
// on this class which is terrible. This store should just listen for events
// and keep itself up to date.
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
if (ev.getType() !== 'im.vector.modular.widgets') return;
if (ev.getStateKey() === this._persistentWidgetId) {
this.destroyPersistentWidget(this._persistentWidgetId);
}
}
destroyPersistentWidget(id) {
if (id !== this._persistentWidgetId) return;
const toDeleteId = this._persistentWidgetId;
WidgetMessagingStore.instance.stopMessagingById(id);
this.setWidgetPersistence(toDeleteId, false);
this.delRoomId(toDeleteId);
}
setWidgetPersistence(widgetId, val) {
if (this._persistentWidgetId === widgetId && !val) {
this._persistentWidgetId = null;
} else if (this._persistentWidgetId !== widgetId && val) {
this._persistentWidgetId = widgetId;
}
this.emit('update');
}
getWidgetPersistence(widgetId) {
return this._persistentWidgetId === widgetId;
}
getPersistentWidgetId() {
return this._persistentWidgetId;
}
getRoomId(widgetId) {
return this._roomIdByWidgetId[widgetId];
}
setRoomId(widgetId, roomId) {
this._roomIdByWidgetId[widgetId] = roomId;
this.emit('update');
}
delRoomId(widgetId) {
delete this._roomIdByWidgetId[widgetId];
this.emit('update');
}
}
if (global.singletonActiveWidgetStore === undefined) {
global.singletonActiveWidgetStore = new ActiveWidgetStore();
}
export default global.singletonActiveWidgetStore;

View File

@ -0,0 +1,112 @@
/*
Copyright 2018 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 EventEmitter from 'events';
import { MatrixEvent } from "matrix-js-sdk";
import { MatrixClientPeg } from '../MatrixClientPeg';
import { WidgetMessagingStore } from "./widgets/WidgetMessagingStore";
export enum ActiveWidgetStoreEvent {
Update = "update",
}
/**
* Stores information about the widgets active in the app right now:
* * What widget is set to remain always-on-screen, if any
* Only one widget may be 'always on screen' at any one time.
* * Negotiated capabilities for active apps
*/
export default class ActiveWidgetStore extends EventEmitter {
private static internalInstance: ActiveWidgetStore;
private persistentWidgetId: string;
// What room ID each widget is associated with (if it's a room widget)
private roomIdByWidgetId = new Map<string, string>();
public static get instance(): ActiveWidgetStore {
if (!ActiveWidgetStore.internalInstance) {
ActiveWidgetStore.internalInstance = new ActiveWidgetStore();
}
return ActiveWidgetStore.internalInstance;
}
public start(): void {
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
}
public stop(): void {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
this.roomIdByWidgetId.clear();
}
private onRoomStateEvents = (ev: MatrixEvent): void => {
// XXX: This listens for state events in order to remove the active widget.
// Everything else relies on views listening for events and calling setters
// on this class which is terrible. This store should just listen for events
// and keep itself up to date.
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
if (ev.getType() !== 'im.vector.modular.widgets') return;
if (ev.getStateKey() === this.persistentWidgetId) {
this.destroyPersistentWidget(this.persistentWidgetId);
}
};
public destroyPersistentWidget(id: string): void {
if (id !== this.persistentWidgetId) return;
const toDeleteId = this.persistentWidgetId;
WidgetMessagingStore.instance.stopMessagingById(id);
this.setWidgetPersistence(toDeleteId, false);
this.delRoomId(toDeleteId);
}
public setWidgetPersistence(widgetId: string, val: boolean): void {
if (this.persistentWidgetId === widgetId && !val) {
this.persistentWidgetId = null;
} else if (this.persistentWidgetId !== widgetId && val) {
this.persistentWidgetId = widgetId;
}
this.emit(ActiveWidgetStoreEvent.Update);
}
public getWidgetPersistence(widgetId: string): boolean {
return this.persistentWidgetId === widgetId;
}
public getPersistentWidgetId(): string {
return this.persistentWidgetId;
}
public getRoomId(widgetId: string): string {
return this.roomIdByWidgetId.get(widgetId);
}
public setRoomId(widgetId: string, roomId: string): void {
this.roomIdByWidgetId.set(widgetId, roomId);
this.emit(ActiveWidgetStoreEvent.Update);
}
public delRoomId(widgetId: string): void {
this.roomIdByWidgetId.delete(widgetId);
this.emit(ActiveWidgetStoreEvent.Update);
}
}
window.mxActiveWidgetStore = ActiveWidgetStore.instance;

View File

@ -142,14 +142,14 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
// If a persistent widget is active, check to see if it's just been removed.
// If it has, it needs to destroyed otherwise unmounting the node won't kill it
const persistentWidgetId = ActiveWidgetStore.getPersistentWidgetId();
const persistentWidgetId = ActiveWidgetStore.instance.getPersistentWidgetId();
if (persistentWidgetId) {
if (
ActiveWidgetStore.getRoomId(persistentWidgetId) === room.roomId &&
ActiveWidgetStore.instance.getRoomId(persistentWidgetId) === room.roomId &&
!roomInfo.widgets.some(w => w.id === persistentWidgetId)
) {
logger.log(`Persistent widget ${persistentWidgetId} removed from room ${room.roomId}: destroying.`);
ActiveWidgetStore.destroyPersistentWidget(persistentWidgetId);
ActiveWidgetStore.instance.destroyPersistentWidget(persistentWidgetId);
}
}
@ -195,7 +195,7 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
// A persistent conference widget indicates that we're participating
const widgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type));
return widgets.some(w => ActiveWidgetStore.getWidgetPersistence(w.id));
return widgets.some(w => ActiveWidgetStore.instance.getWidgetPersistence(w.id));
}
}

View File

@ -266,7 +266,7 @@ export class StopGapWidget extends EventEmitter {
WidgetMessagingStore.instance.storeMessaging(this.mockWidget, this.messaging);
if (!this.appTileProps.userWidget && this.appTileProps.room) {
ActiveWidgetStore.setRoomId(this.mockWidget.id, this.appTileProps.room.roomId);
ActiveWidgetStore.instance.setRoomId(this.mockWidget.id, this.appTileProps.room.roomId);
}
// Always attach a handler for ViewRoom, but permission check it internally
@ -319,7 +319,7 @@ export class StopGapWidget extends EventEmitter {
if (WidgetType.JITSI.matches(this.mockWidget.type)) {
CountlyAnalytics.instance.trackJoinCall(this.appTileProps.room.roomId, true, true);
}
ActiveWidgetStore.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value);
ActiveWidgetStore.instance.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value);
ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack
}
@ -406,13 +406,13 @@ export class StopGapWidget extends EventEmitter {
}
public stop(opts = { forceDestroy: false }) {
if (!opts?.forceDestroy && ActiveWidgetStore.getPersistentWidgetId() === this.mockWidget.id) {
if (!opts?.forceDestroy && ActiveWidgetStore.instance.getPersistentWidgetId() === this.mockWidget.id) {
logger.log("Skipping destroy - persistent widget");
return;
}
if (!this.started) return;
WidgetMessagingStore.instance.stopMessaging(this.mockWidget);
ActiveWidgetStore.delRoomId(this.mockWidget.id);
ActiveWidgetStore.instance.delRoomId(this.mockWidget.id);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().off('event', this.onEvent);

View File

@ -14,9 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { IInstance } from "matrix-js-sdk/src/client";
import { Protocols } from "../components/views/directory/NetworkDropdown";
// Find a protocol 'instance' with a given instance_id
// in the supplied protocols dict
export function instanceForInstanceId(protocols, instanceId) {
export function instanceForInstanceId(protocols: Protocols, instanceId: string): IInstance {
if (!instanceId) return null;
for (const proto of Object.keys(protocols)) {
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;
@ -28,7 +31,7 @@ export function instanceForInstanceId(protocols, instanceId) {
// given an instance_id, return the name of the protocol for
// that instance ID in the supplied protocols dict
export function protocolNameForInstanceId(protocols, instanceId) {
export function protocolNameForInstanceId(protocols: Protocols, instanceId: string): string {
if (!instanceId) return null;
for (const proto of Object.keys(protocols)) {
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;

View File

@ -17,7 +17,7 @@ limitations under the License.
import SdkConfig from '../SdkConfig';
import { MatrixClientPeg } from '../MatrixClientPeg';
export function getHostingLink(campaign) {
export function getHostingLink(campaign: string): string {
const hostingLink = SdkConfig.get().hosting_signup_link;
if (!hostingLink) return null;
if (!campaign) return hostingLink;
@ -27,7 +27,7 @@ export function getHostingLink(campaign) {
try {
const hostingUrl = new URL(hostingLink);
hostingUrl.searchParams.set("utm_campaign", campaign);
return hostingUrl.format();
return hostingUrl.toString();
} catch (e) {
return hostingLink;
}

View File

@ -17,14 +17,14 @@ limitations under the License.
import { MatrixClientPeg } from '../MatrixClientPeg';
import { _t } from '../languageHandler';
export function getNameForEventRoom(userId, roomId) {
export function getNameForEventRoom(userId: string, roomId: string): string {
const client = MatrixClientPeg.get();
const room = client.getRoom(roomId);
const member = room && room.getMember(userId);
return member ? member.name : userId;
}
export function userLabelForEventRoom(userId, roomId) {
export function userLabelForEventRoom(userId: string, roomId: string): string {
const name = getNameForEventRoom(userId, roomId);
if (name !== userId) {
return _t("%(name)s (%(userId)s)", { name, userId });

View File

@ -26,17 +26,17 @@ const subtleCrypto = window.crypto.subtle || window.crypto.webkitSubtle;
* Make an Error object which has a friendlyText property which is already
* translated and suitable for showing to the user.
*
* @param {string} msg message for the exception
* @param {string} message message for the exception
* @param {string} friendlyText
* @returns {Error}
* @returns {{message: string, friendlyText: string}}
*/
function friendlyError(msg, friendlyText) {
const e = new Error(msg);
e.friendlyText = friendlyText;
return e;
function friendlyError(
message: string, friendlyText: string,
): { message: string, friendlyText: string } {
return { message, friendlyText };
}
function cryptoFailMsg() {
function cryptoFailMsg(): string {
return _t('Your browser does not support the required cryptography extensions');
}
@ -49,7 +49,7 @@ function cryptoFailMsg() {
*
*
*/
export async function decryptMegolmKeyFile(data, password) {
export async function decryptMegolmKeyFile(data: ArrayBuffer, password: string): Promise<string> {
const body = unpackMegolmKeyFile(data);
const brand = SdkConfig.get().brand;
@ -124,7 +124,11 @@ export async function decryptMegolmKeyFile(data, password) {
* key-derivation function.
* @return {Promise<ArrayBuffer>} promise for encrypted output
*/
export async function encryptMegolmKeyFile(data, password, options) {
export async function encryptMegolmKeyFile(
data: string,
password: string,
options?: { kdf_rounds?: number }, // eslint-disable-line camelcase
): Promise<ArrayBuffer> {
options = options || {};
const kdfRounds = options.kdf_rounds || 500000;
@ -196,7 +200,7 @@ export async function encryptMegolmKeyFile(data, password, options) {
* @param {String} password password
* @return {Promise<[CryptoKey, CryptoKey]>} promise for [aes key, hmac key]
*/
async function deriveKeys(salt, iterations, password) {
async function deriveKeys(salt: Uint8Array, iterations: number, password: string): Promise<[CryptoKey, CryptoKey]> {
const start = new Date();
let key;
@ -229,7 +233,7 @@ async function deriveKeys(salt, iterations, password) {
}
const now = new Date();
logger.log("E2e import/export: deriveKeys took " + (now - start) + "ms");
logger.log("E2e import/export: deriveKeys took " + (now.getTime() - start.getTime()) + "ms");
const aesKey = keybits.slice(0, 32);
const hmacKey = keybits.slice(32);
@ -271,7 +275,7 @@ const TRAILER_LINE = '-----END MEGOLM SESSION DATA-----';
* @param {ArrayBuffer} data input file
* @return {Uint8Array} unbase64ed content
*/
function unpackMegolmKeyFile(data) {
function unpackMegolmKeyFile(data: ArrayBuffer): Uint8Array {
// parse the file as a great big String. This should be safe, because there
// should be no non-ASCII characters, and it means that we can do string
// comparisons to find the header and footer, and feed it into window.atob.
@ -279,6 +283,7 @@ function unpackMegolmKeyFile(data) {
// look for the start line
let lineStart = 0;
// eslint-disable-next-line no-constant-condition
while (1) {
const lineEnd = fileStr.indexOf('\n', lineStart);
if (lineEnd < 0) {
@ -297,6 +302,7 @@ function unpackMegolmKeyFile(data) {
const dataStart = lineStart;
// look for the end line
// eslint-disable-next-line no-constant-condition
while (1) {
const lineEnd = fileStr.indexOf('\n', lineStart);
const line = fileStr.slice(lineStart, lineEnd < 0 ? undefined : lineEnd).trim();
@ -324,7 +330,7 @@ function unpackMegolmKeyFile(data) {
* @param {Uint8Array} data raw data
* @return {ArrayBuffer} formatted file
*/
function packMegolmKeyFile(data) {
function packMegolmKeyFile(data: Uint8Array): ArrayBuffer {
// we split into lines before base64ing, because encodeBase64 doesn't deal
// terribly well with large arrays.
const LINE_LENGTH = (72 * 4 / 3);
@ -347,7 +353,7 @@ function packMegolmKeyFile(data) {
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The base64.
*/
function encodeBase64(uint8Array) {
function encodeBase64(uint8Array: Uint8Array): string {
// Misinterpt the Uint8Array as Latin-1.
// window.btoa expects a unicode string with codepoints in the range 0-255.
const latin1String = String.fromCharCode.apply(null, uint8Array);
@ -360,7 +366,7 @@ function encodeBase64(uint8Array) {
* @param {string} base64 The base64 to decode.
* @return {Uint8Array} The decoded data.
*/
function decodeBase64(base64) {
function decodeBase64(base64: string): Uint8Array {
// window.atob returns a unicode string with codepoints in the range 0-255.
const latin1String = window.atob(base64);
// Encode the string as a Uint8Array