mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 21:24:59 +08:00
Merge remote-tracking branch 'upstream/develop' into task/messages-ts
This commit is contained in:
commit
6a88ac900c
12
src/@types/global.d.ts
vendored
12
src/@types/global.d.ts
vendored
@ -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 */
|
||||
|
@ -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();
|
||||
|
@ -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 });
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
112
src/stores/ActiveWidgetStore.ts
Normal file
112
src/stores/ActiveWidgetStore.ts
Normal 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;
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
@ -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;
|
||||
}
|
@ -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 });
|
@ -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
|
Loading…
Reference in New Issue
Block a user