Merge pull request #6864 from SimonBrandner/task/rageshake-ts

This commit is contained in:
Germain 2021-09-27 09:25:05 +01:00 committed by GitHub
commit 3e34f72378
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 37 deletions

View file

@ -49,6 +49,7 @@ import PerformanceMonitor from "../performance";
import UIStore from "../stores/UIStore"; import UIStore from "../stores/UIStore";
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore"; import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore"; import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake";
import ActiveWidgetStore from "../stores/ActiveWidgetStore"; import ActiveWidgetStore from "../stores/ActiveWidgetStore";
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
@ -225,6 +226,15 @@ declare global {
) => string; ) => string;
isReady: () => boolean; 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 */ /* eslint-enable @typescript-eslint/naming-convention */

View file

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