Use interface ILogItem

This commit is contained in:
RMidhunSuresh 2021-11-15 17:29:08 +05:30
parent a7d059b3ed
commit 520e0f1b89
23 changed files with 155 additions and 114 deletions

View file

@ -184,7 +184,7 @@ import {Clock as MockClock} from "../../../../mocks/Clock.js";
import {createMockStorage} from "../../../../mocks/Storage"; import {createMockStorage} from "../../../../mocks/Storage";
import {ListObserver} from "../../../../mocks/ListObserver.js"; import {ListObserver} from "../../../../mocks/ListObserver.js";
import {createEvent, withTextBody, withContent} from "../../../../mocks/event.js"; import {createEvent, withTextBody, withContent} from "../../../../mocks/event.js";
import {NullLogItem, NullLogger} from "../../../../logging/NullLogger.js"; import {NullLogItem, NullLogger} from "../../../../logging/NullLogger";
import {HomeServer as MockHomeServer} from "../../../../mocks/HomeServer.js"; import {HomeServer as MockHomeServer} from "../../../../mocks/HomeServer.js";
// other imports // other imports
import {BaseMessageTile} from "./tiles/BaseMessageTile.js"; import {BaseMessageTile} from "./tiles/BaseMessageTile.js";

View file

@ -17,14 +17,14 @@ limitations under the License.
import {LogItem} from "./LogItem"; import {LogItem} from "./LogItem";
import {LogLevel, LogFilter} from "./LogFilter"; import {LogLevel, LogFilter} from "./LogFilter";
import type {FilterCreator, LabelOrValues, LogCallback} from "./LogItem"; import type {FilterCreator, LabelOrValues, LogCallback, ILogItem} from "./LogItem";
import type {LogLevelOrNull} from "./LogFilter"; import type {LogLevelOrNull} from "./LogFilter";
// todo: should this import be here just for getting the type? should it instead be done when Platform.js --> Platform.ts? // todo: should this import be here just for getting the type? should it instead be done when Platform.js --> Platform.ts?
import type {Platform} from "../platform/web/Platform.js"; import type {Platform} from "../platform/web/Platform.js";
export abstract class BaseLogger { export abstract class BaseLogger {
protected _openItems: Set<LogItem> = new Set(); protected _openItems: Set<ILogItem> = new Set();
protected _platform: Platform; protected _platform: Platform;
constructor({platform}) { constructor({platform}) {
@ -38,7 +38,7 @@ export abstract class BaseLogger {
} }
/** if item is a log item, wrap the callback in a child of it, otherwise start a new root log item. */ /** if item is a log item, wrap the callback in a child of it, otherwise start a new root log item. */
wrapOrRun(item: LogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): unknown { wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): unknown {
if (item) { if (item) {
return item.wrap(labelOrValues, callback, logLevel, filterCreator); return item.wrap(labelOrValues, callback, logLevel, filterCreator);
} else { } else {
@ -51,7 +51,7 @@ export abstract class BaseLogger {
Useful to pair with LogItem.refDetached. Useful to pair with LogItem.refDetached.
@return {LogItem} the log item added, useful to pass to LogItem.refDetached */ @return {LogItem} the log item added, useful to pass to LogItem.refDetached */
runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): LogItem { runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): ILogItem {
// todo: Remove jsdoc type? // todo: Remove jsdoc type?
if (logLevel === null) { if (logLevel === null) {
logLevel = LogLevel.Info; logLevel = LogLevel.Info;
@ -72,7 +72,7 @@ export abstract class BaseLogger {
return this._run(item, callback, logLevel!, filterCreator, true); return this._run(item, callback, logLevel!, filterCreator, true);
} }
_run(item: LogItem, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator, shouldThrow: boolean): unknown { _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator, shouldThrow: boolean): unknown {
this._openItems.add(item); this._openItems.add(item);
const finishItem = () => { const finishItem = () => {
@ -135,7 +135,7 @@ export abstract class BaseLogger {
this._openItems.clear(); this._openItems.clear();
} }
abstract _persistItem(item: LogItem, filter?: LogFilter, forced?: boolean): void; abstract _persistItem(item: ILogItem, filter?: LogFilter, forced?: boolean): void;
abstract export(): void; abstract export(): void;

View file

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {BaseLogger} from "./BaseLogger"; import {BaseLogger} from "./BaseLogger";
import type {LogItem, LogItemValues} from "./LogItem"; import type {ILogItem, LogItemValues} from "./LogItem";
export class ConsoleLogger extends BaseLogger { export class ConsoleLogger extends BaseLogger {
_persistItem(item: LogItem): void { _persistItem(item: ILogItem): void {
printToConsole(item); printToConsole(item);
} }
@ -41,7 +41,7 @@ function filterValues(values: LogItemValues): LogItemValues | null {
}, null); }, null);
} }
function printToConsole(item: LogItem): void { function printToConsole(item: ILogItem): void {
const label = `${itemCaption(item)} (${item.duration}ms)`; const label = `${itemCaption(item)} (${item.duration}ms)`;
const filteredValues = filterValues(item.values); const filteredValues = filterValues(item.values);
const shouldGroup = item.children || filteredValues; const shouldGroup = item.children || filteredValues;
@ -74,7 +74,7 @@ function printToConsole(item: LogItem): void {
} }
} }
function itemCaption(item: LogItem): string { function itemCaption(item: ILogItem): string {
if (item.values.t === "network") { if (item.values.t === "network") {
return `${item.values.method} ${item.values.url}`; return `${item.values.method} ${item.values.url}`;
} else if (item.values.l && typeof item.values.id !== "undefined") { } else if (item.values.l && typeof item.values.id !== "undefined") {

View file

@ -26,7 +26,7 @@ import {BaseLogger} from "./BaseLogger";
import type {Interval} from "../platform/web/dom/Clock"; import type {Interval} from "../platform/web/dom/Clock";
import type {Platform} from "../platform/web/Platform.js"; import type {Platform} from "../platform/web/Platform.js";
import type {BlobHandle} from "../platform/web/dom/BlobHandle.js"; import type {BlobHandle} from "../platform/web/dom/BlobHandle.js";
import type {LogItem} from "./LogItem"; import type {ILogItem} from "./LogItem";
import type {LogFilter} from "./LogFilter"; import type {LogFilter} from "./LogFilter";
type QueuedItem = { type QueuedItem = {
@ -116,8 +116,8 @@ export class IDBLogger extends BaseLogger {
return openDatabase(this._name, db => db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}), 1); return openDatabase(this._name, db => db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}), 1);
} }
_persistItem(logItem: LogItem, filter: LogFilter, forced: boolean): void { _persistItem(logItem: ILogItem, filter: LogFilter, forced: boolean): void {
const serializedItem = logItem.serialize(filter, undefined, forced); const serializedItem = logItem.serialize(filter, null, forced);
this._queuedItems.push({ this._queuedItems.push({
json: JSON.stringify(serializedItem) json: JSON.stringify(serializedItem)
}); });

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import type {LogItem} from "./LogItem"; import type {ILogItem} from "./LogItem";
export enum LogLevel { export enum LogLevel {
All = 1, All = 1,
@ -37,7 +37,7 @@ export class LogFilter {
this._parentFilter = parentFilter; this._parentFilter = parentFilter;
} }
filter(item: LogItem, children: Array<unknown> | null): boolean { filter(item: ILogItem, children: Array<unknown> | null): boolean {
if (this._parentFilter) { if (this._parentFilter) {
if (!this._parentFilter.filter(item, children)) { if (!this._parentFilter.filter(item, children)) {
return false; return false;

View file

@ -33,6 +33,30 @@ interface ISerializedItem {
c?: Array<ISerializedItem>; c?: Array<ISerializedItem>;
}; };
export interface ILogItem {
logger: any;
level: typeof LogLevel;
duration?: number;
end?: number | null;
start?: number;
logLevel: LogLevel;
children: Array<ILogItem> | null;
values: LogItemValues;
error: Error | null;
wrap(labelOrValues: LabelOrValues, callback: LogCallback, level: LogLevelOrNull, filterCreator: FilterCreator): unknown;
log(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull): void;
set(key: string | object, value: unknown): void;
run(callback: LogCallback): unknown;
runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator): ILogItem;
wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator): void;
refDetached(logItem: ILogItem, logLevel: LogLevelOrNull): void;
ensureRefId(): void;
catch(err: Error): Error;
finish(): void;
child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator): ILogItem;
serialize(filter: LogFilter, parentStartTime: number | null, forced: boolean): ISerializedItem | null;
}
export type LogItemValues = { export type LogItemValues = {
l?: string; l?: string;
t?: string; t?: string;
@ -44,10 +68,10 @@ export type LogItemValues = {
} }
export type LabelOrValues = string | LogItemValues; export type LabelOrValues = string | LogItemValues;
export type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | null; export type FilterCreator = ((filter: LogFilter, item: ILogItem) => LogFilter) | null;
export type LogCallback = (item: LogItem) => unknown; export type LogCallback = (item: ILogItem) => unknown;
export class LogItem { export class LogItem implements ILogItem {
public readonly start: number; public readonly start: number;
public logLevel: LogLevel; public logLevel: LogLevel;
public error: Error | null; public error: Error | null;
@ -70,7 +94,7 @@ export class LogItem {
} }
/** start a new root log item and run it detached mode, see BaseLogger.runDetached */ /** start a new root log item and run it detached mode, see BaseLogger.runDetached */
runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator): LogItem { runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator): ILogItem {
return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator); return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator);
} }
@ -81,9 +105,9 @@ export class LogItem {
/** logs a reference to a different log item, usually obtained from runDetached. /** logs a reference to a different log item, usually obtained from runDetached.
This is useful if the referenced operation can't be awaited. */ This is useful if the referenced operation can't be awaited. */
refDetached(logItem: LogItem, logLevel: LogLevelOrNull = null): void { refDetached(logItem: ILogItem, logLevel: LogLevelOrNull = null): void {
logItem.ensureRefId(); logItem.ensureRefId();
this.log({ref: logItem._values.refId as number}, logLevel); this.log({ref: (logItem as LogItem)._values.refId}, logLevel);
} }
ensureRefId(): void { ensureRefId(): void {
@ -267,7 +291,7 @@ export class LogItem {
return err; return err;
} }
child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator): LogItem { child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator): ILogItem {
if (this.end !== null) { if (this.end !== null) {
console.trace("log item is finished, additional logs will likely not be recorded"); console.trace("log item is finished, additional logs will likely not be recorded");
} }

View file

@ -14,18 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {LogLevel} from "./LogFilter"; import {LogLevel} from "./LogFilter";
import type {ILogItem, LabelOrValues, LogCallback, LogItemValues} from "./LogItem";
function noop () {} function noop (): void {}
export class NullLogger { export class NullLogger {
constructor() { public readonly item: ILogItem = new NullLogItem(this);
this.item = new NullLogItem(this);
}
log() {} log(): void {}
run(_, callback) { run(_: null, callback) {
return callback(this.item); return callback(this.item);
} }
@ -50,50 +49,61 @@ export class NullLogger {
} }
} }
export class NullLogItem { export class NullLogItem implements ILogItem {
constructor(logger) { public readonly logger: NullLogger;
public readonly logLevel: LogLevel;
public children: Array<ILogItem> | null = null;
public values: LogItemValues;
public error: Error | null = null;
constructor(logger: NullLogger) {
this.logger = logger; this.logger = logger;
} }
wrap(_, callback) { wrap(_: LabelOrValues, callback: LogCallback): unknown {
return callback(this); return callback(this);
} }
log() {} log(): void {}
set() {} set(): void {}
runDetached(_, callback) { runDetached(_: LabelOrValues, callback: LogCallback): ILogItem {
new Promise(r => r(callback(this))).then(noop, noop); new Promise(r => r(callback(this))).then(noop, noop);
}
wrapDetached(_, callback) {
return this.refDetached(null, callback);
}
run(callback) {
return callback(this);
}
refDetached() {}
ensureRefId() {}
get level() {
return LogLevel;
}
get duration() {
return 0;
}
catch(err) {
return err;
}
child() {
return this; return this;
} }
finish() {} wrapDetached(_: LabelOrValues, _callback: LogCallback): void {
return this.refDetached();
}
run(callback: LogCallback): unknown {
return callback(this);
}
refDetached(): void {}
ensureRefId(): void {}
get level(): typeof LogLevel {
return LogLevel;
}
get duration(): 0 {
return 0;
}
catch(err: Error): Error {
return err;
}
child() {
return this;
}
finish(): void {}
serialize() {
return null;
}
} }
export const Instance = new NullLogger(); export const Instance = new NullLogger();

View file

@ -1,7 +1,7 @@
// these are helper functions if you can't assume you always have a log item (e.g. some code paths call with one set, others don't) // these are helper functions if you can't assume you always have a log item (e.g. some code paths call with one set, others don't)
// if you know you always have a log item, better to use the methods on the log item than these utility functions. // if you know you always have a log item, better to use the methods on the log item than these utility functions.
import {Instance as NullLoggerInstance} from "./NullLogger.js"; import {Instance as NullLoggerInstance} from "./NullLogger";
export function wrapOrRunNullLogger(logItem, labelOrValues, callback, logLevel = null, filterCreator = null) { export function wrapOrRunNullLogger(logItem, labelOrValues, callback, logLevel = null, filterCreator = null) {
if (logItem) { if (logItem) {

View file

@ -26,7 +26,7 @@ import type {OlmWorker} from "../OlmWorker";
import type {Transaction} from "../../storage/idb/Transaction"; import type {Transaction} from "../../storage/idb/Transaction";
import type {TimelineEvent} from "../../storage/types"; import type {TimelineEvent} from "../../storage/types";
import type {DecryptionResult} from "../DecryptionResult"; import type {DecryptionResult} from "../DecryptionResult";
import type {LogItem} from "../../../logging/LogItem"; import type {ILogItem} from "../../../logging/LogItem";
export class Decryption { export class Decryption {
private keyLoader: KeyLoader; private keyLoader: KeyLoader;
@ -136,7 +136,7 @@ export class Decryption {
* Extracts room keys from decrypted device messages. * Extracts room keys from decrypted device messages.
* The key won't be persisted yet, you need to call RoomKey.write for that. * The key won't be persisted yet, you need to call RoomKey.write for that.
*/ */
roomKeysFromDeviceMessages(decryptionResults: DecryptionResult[], log: LogItem): IncomingRoomKey[] { roomKeysFromDeviceMessages(decryptionResults: DecryptionResult[], log: ILogItem): IncomingRoomKey[] {
const keys: IncomingRoomKey[] = []; const keys: IncomingRoomKey[] = [];
for (const dr of decryptionResults) { for (const dr of decryptionResults) {
if (dr.event?.type !== "m.room_key" || dr.event.content?.algorithm !== MEGOLM_ALGORITHM) { if (dr.event?.type !== "m.room_key" || dr.event.content?.algorithm !== MEGOLM_ALGORITHM) {
@ -152,7 +152,7 @@ export class Decryption {
log.logLevel = log.level.Warn; log.logLevel = log.level.Warn;
log.set("invalid", true); log.set("invalid", true);
} }
}, log.level.Detail); }, log.level.Detail, null);
} }
return keys; return keys;
} }

View file

@ -244,7 +244,7 @@ export class Invite extends EventEmitter {
} }
} }
import {NullLogItem} from "../../logging/NullLogger.js"; import {NullLogItem} from "../../logging/NullLogger";
import {Clock as MockClock} from "../../mocks/Clock.js"; import {Clock as MockClock} from "../../mocks/Clock.js";
import {default as roomInviteFixture} from "../../fixtures/matrix/invites/room.js"; import {default as roomInviteFixture} from "../../fixtures/matrix/invites/room.js";
import {default as dmInviteFixture} from "../../fixtures/matrix/invites/dm.js"; import {default as dmInviteFixture} from "../../fixtures/matrix/invites/dm.js";

View file

@ -353,7 +353,7 @@ export class SendQueue {
import {HomeServer as MockHomeServer} from "../../../mocks/HomeServer.js"; import {HomeServer as MockHomeServer} from "../../../mocks/HomeServer.js";
import {createMockStorage} from "../../../mocks/Storage"; import {createMockStorage} from "../../../mocks/Storage";
import {ListObserver} from "../../../mocks/ListObserver.js"; import {ListObserver} from "../../../mocks/ListObserver.js";
import {NullLogger, NullLogItem} from "../../../logging/NullLogger.js"; import {NullLogger, NullLogItem} from "../../../logging/NullLogger";
import {createEvent, withTextBody, withTxnId} from "../../../mocks/event.js"; import {createEvent, withTextBody, withTxnId} from "../../../mocks/event.js";
import {poll} from "../../../mocks/poll.js"; import {poll} from "../../../mocks/poll.js";
import {createAnnotation} from "../timeline/relations.js"; import {createAnnotation} from "../timeline/relations.js";

View file

@ -346,7 +346,7 @@ import {Clock as MockClock} from "../../../mocks/Clock.js";
import {createMockStorage} from "../../../mocks/Storage"; import {createMockStorage} from "../../../mocks/Storage";
import {ListObserver} from "../../../mocks/ListObserver.js"; import {ListObserver} from "../../../mocks/ListObserver.js";
import {createEvent, withTextBody, withContent, withSender} from "../../../mocks/event.js"; import {createEvent, withTextBody, withContent, withSender} from "../../../mocks/event.js";
import {NullLogItem} from "../../../logging/NullLogger.js"; import {NullLogItem} from "../../../logging/NullLogger";
import {EventEntry} from "./entries/EventEntry.js"; import {EventEntry} from "./entries/EventEntry.js";
import {User} from "../../User.js"; import {User} from "../../User.js";
import {PendingEvent} from "../sending/PendingEvent.js"; import {PendingEvent} from "../sending/PendingEvent.js";

View file

@ -205,7 +205,7 @@ import {FragmentIdComparer} from "../FragmentIdComparer.js";
import {RelationWriter} from "./RelationWriter.js"; import {RelationWriter} from "./RelationWriter.js";
import {createMockStorage} from "../../../../mocks/Storage"; import {createMockStorage} from "../../../../mocks/Storage";
import {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.js"; import {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.js";
import {NullLogItem} from "../../../../logging/NullLogger.js"; import {NullLogItem} from "../../../../logging/NullLogger";
import {TimelineMock, eventIds, eventId} from "../../../../mocks/TimelineMock.ts"; import {TimelineMock, eventIds, eventId} from "../../../../mocks/TimelineMock.ts";
import {SyncWriter} from "./SyncWriter.js"; import {SyncWriter} from "./SyncWriter.js";
import {MemberWriter} from "./MemberWriter.js"; import {MemberWriter} from "./MemberWriter.js";

View file

@ -257,7 +257,7 @@ import {createMockStorage} from "../../../../mocks/Storage";
import {createEvent, withTextBody, withRedacts, withContent} from "../../../../mocks/event.js"; import {createEvent, withTextBody, withRedacts, withContent} from "../../../../mocks/event.js";
import {createAnnotation} from "../relations.js"; import {createAnnotation} from "../relations.js";
import {FragmentIdComparer} from "../FragmentIdComparer.js"; import {FragmentIdComparer} from "../FragmentIdComparer.js";
import {NullLogItem} from "../../../../logging/NullLogger.js"; import {NullLogItem} from "../../../../logging/NullLogger";
export function tests() { export function tests() {
const fragmentIdComparer = new FragmentIdComparer([]); const fragmentIdComparer = new FragmentIdComparer([]);

View file

@ -258,7 +258,7 @@ export class SyncWriter {
import {createMockStorage} from "../../../../mocks/Storage"; import {createMockStorage} from "../../../../mocks/Storage";
import {createEvent, withTextBody} from "../../../../mocks/event.js"; import {createEvent, withTextBody} from "../../../../mocks/event.js";
import {Instance as nullLogger} from "../../../../logging/NullLogger.js"; import {Instance as nullLogger} from "../../../../logging/NullLogger";
export function tests() { export function tests() {
const roomId = "!abc:hs.tld"; const roomId = "!abc:hs.tld";
return { return {

View file

@ -16,7 +16,7 @@ limitations under the License.
import {iterateCursor, DONE, NOT_DONE, reqAsPromise} from "./utils"; import {iterateCursor, DONE, NOT_DONE, reqAsPromise} from "./utils";
import {StorageError} from "../common"; import {StorageError} from "../common";
import {LogItem} from "../../../logging/LogItem"; import {ILogItem} from "../../../logging/LogItem";
import {IDBKey} from "./Transaction"; import {IDBKey} from "./Transaction";
// this is the part of the Transaction class API that is used here and in the Store subclass, // this is the part of the Transaction class API that is used here and in the Store subclass,
@ -25,7 +25,7 @@ export interface ITransaction {
idbFactory: IDBFactory; idbFactory: IDBFactory;
IDBKeyRange: typeof IDBKeyRange; IDBKeyRange: typeof IDBKeyRange;
databaseName: string; databaseName: string;
addWriteError(error: StorageError, refItem: LogItem | undefined, operationName: string, keys: IDBKey[] | undefined); addWriteError(error: StorageError, refItem: ILogItem | undefined, operationName: string, keys: IDBKey[] | undefined);
} }
type Reducer<A,B> = (acc: B, val: A) => B type Reducer<A,B> = (acc: B, val: A) => B
@ -277,7 +277,7 @@ export function tests() {
class MockTransaction extends MockIDBImpl { class MockTransaction extends MockIDBImpl {
get databaseName(): string { return "mockdb"; } get databaseName(): string { return "mockdb"; }
addWriteError(error: StorageError, refItem: LogItem | undefined, operationName: string, keys: IDBKey[] | undefined) {} addWriteError(error: StorageError, refItem: ILogItem | undefined, operationName: string, keys: IDBKey[] | undefined) {}
} }
interface TestEntry { interface TestEntry {

View file

@ -21,10 +21,10 @@ import { exportSession, importSession, Export } from "./export";
import { schema } from "./schema"; import { schema } from "./schema";
import { detectWebkitEarlyCloseTxnBug } from "./quirks"; import { detectWebkitEarlyCloseTxnBug } from "./quirks";
import { BaseLogger } from "../../../logging/BaseLogger"; import { BaseLogger } from "../../../logging/BaseLogger";
import { LogItem } from "../../../logging/LogItem"; import { ILogItem } from "../../../logging/LogItem";
const sessionName = (sessionId: string) => `hydrogen_session_${sessionId}`; const sessionName = (sessionId: string) => `hydrogen_session_${sessionId}`;
const openDatabaseWithSessionId = function(sessionId: string, idbFactory: IDBFactory, localStorage: IDOMStorage, log: LogItem) { const openDatabaseWithSessionId = function(sessionId: string, idbFactory: IDBFactory, localStorage: IDOMStorage, log: ILogItem) {
const create = (db, txn, oldVersion, version) => createStores(db, txn, oldVersion, version, localStorage, log); const create = (db, txn, oldVersion, version) => createStores(db, txn, oldVersion, version, localStorage, log);
return openDatabase(sessionName(sessionId), create, schema.length, idbFactory); return openDatabase(sessionName(sessionId), create, schema.length, idbFactory);
} }
@ -63,7 +63,7 @@ export class StorageFactory {
this._localStorage = localStorage; this._localStorage = localStorage;
} }
async create(sessionId: string, log: LogItem): Promise<Storage> { async create(sessionId: string, log: ILogItem): Promise<Storage> {
await this._serviceWorkerHandler?.preventConcurrentSessionAccess(sessionId); await this._serviceWorkerHandler?.preventConcurrentSessionAccess(sessionId);
requestPersistedStorage().then(persisted => { requestPersistedStorage().then(persisted => {
// Firefox lies here though, and returns true even if the user denied the request // Firefox lies here though, and returns true even if the user denied the request
@ -83,23 +83,30 @@ export class StorageFactory {
return reqAsPromise(req); return reqAsPromise(req);
} }
async export(sessionId: string, log: LogItem): Promise<Export> { async export(sessionId: string, log: ILogItem): Promise<Export> {
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log); const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log);
return await exportSession(db); return await exportSession(db);
} }
async import(sessionId: string, data: Export, log: LogItem): Promise<void> { async import(sessionId: string, data: Export, log: ILogItem): Promise<void> {
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log); const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log);
return await importSession(db, data); return await importSession(db, data);
} }
} }
async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: number | null, version: number, localStorage: IDOMStorage, log: LogItem): Promise<void> { async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: number | null, version: number, localStorage: IDOMStorage, log: ILogItem): Promise<void> {
const startIdx = oldVersion || 0; const startIdx = oldVersion || 0;
return log.wrap({l: "storage migration", oldVersion, version}, async log => { return log.wrap(
for(let i = startIdx; i < version; ++i) { { l: "storage migration", oldVersion, version },
const migrationFunc = schema[i]; async (log) => {
await log.wrap(`v${i + 1}`, log => migrationFunc(db, txn, localStorage, log)); for (let i = startIdx; i < version; ++i) {
} const migrationFunc = schema[i];
}) as Promise<void>; await log.wrap(`v${i + 1}`, (log) =>
migrationFunc(db, txn, localStorage, log), null, null
);
}
},
null,
null
) as Promise<void>;
} }

View file

@ -18,7 +18,7 @@ import {QueryTarget, IDBQuery, ITransaction} from "./QueryTarget";
import {IDBRequestError, IDBRequestAttemptError} from "./error"; import {IDBRequestError, IDBRequestAttemptError} from "./error";
import {reqAsPromise} from "./utils"; import {reqAsPromise} from "./utils";
import {Transaction, IDBKey} from "./Transaction"; import {Transaction, IDBKey} from "./Transaction";
import {LogItem} from "../../../logging/LogItem"; import {ILogItem} from "../../../logging/LogItem";
const LOG_REQUESTS = false; const LOG_REQUESTS = false;
@ -145,7 +145,7 @@ export class Store<T> extends QueryTarget<T> {
return new QueryTarget<T>(new QueryTargetWrapper<T>(this._idbStore.index(indexName)), this._transaction); return new QueryTarget<T>(new QueryTargetWrapper<T>(this._idbStore.index(indexName)), this._transaction);
} }
put(value: T, log?: LogItem): void { put(value: T, log?: ILogItem): void {
// If this request fails, the error will bubble up to the transaction and abort it, // If this request fails, the error will bubble up to the transaction and abort it,
// which is the behaviour we want. Therefore, it is ok to not create a promise for this // which is the behaviour we want. Therefore, it is ok to not create a promise for this
// request and await it. // request and await it.
@ -160,13 +160,13 @@ export class Store<T> extends QueryTarget<T> {
this._prepareErrorLog(request, log, "put", undefined, value); this._prepareErrorLog(request, log, "put", undefined, value);
} }
add(value: T, log?: LogItem): void { add(value: T, log?: ILogItem): void {
// ok to not monitor result of request, see comment in `put`. // ok to not monitor result of request, see comment in `put`.
const request = this._idbStore.add(value); const request = this._idbStore.add(value);
this._prepareErrorLog(request, log, "add", undefined, value); this._prepareErrorLog(request, log, "add", undefined, value);
} }
async tryAdd(value: T, log: LogItem): Promise<boolean> { async tryAdd(value: T, log: ILogItem): Promise<boolean> {
try { try {
await reqAsPromise(this._idbStore.add(value)); await reqAsPromise(this._idbStore.add(value));
return true; return true;
@ -181,13 +181,13 @@ export class Store<T> extends QueryTarget<T> {
} }
} }
delete(keyOrKeyRange: IDBValidKey | IDBKeyRange, log?: LogItem): void { delete(keyOrKeyRange: IDBValidKey | IDBKeyRange, log?: ILogItem): void {
// ok to not monitor result of request, see comment in `put`. // ok to not monitor result of request, see comment in `put`.
const request = this._idbStore.delete(keyOrKeyRange); const request = this._idbStore.delete(keyOrKeyRange);
this._prepareErrorLog(request, log, "delete", keyOrKeyRange, undefined); this._prepareErrorLog(request, log, "delete", keyOrKeyRange, undefined);
} }
private _prepareErrorLog(request: IDBRequest, log: LogItem | undefined, operationName: string, key: IDBKey | undefined, value: T | undefined) { private _prepareErrorLog(request: IDBRequest, log: ILogItem | undefined, operationName: string, key: IDBKey | undefined, value: T | undefined) {
if (log) { if (log) {
log.ensureRefId(); log.ensureRefId();
} }

View file

@ -36,7 +36,7 @@ import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore";
import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore"; import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore";
import {OperationStore} from "./stores/OperationStore"; import {OperationStore} from "./stores/OperationStore";
import {AccountDataStore} from "./stores/AccountDataStore"; import {AccountDataStore} from "./stores/AccountDataStore";
import {LogItem} from "../../../logging/LogItem"; import {ILogItem} from "../../../logging/LogItem";
import {BaseLogger} from "../../../logging/BaseLogger"; import {BaseLogger} from "../../../logging/BaseLogger";
export type IDBKey = IDBValidKey | IDBKeyRange; export type IDBKey = IDBValidKey | IDBKeyRange;
@ -44,7 +44,7 @@ export type IDBKey = IDBValidKey | IDBKeyRange;
class WriteErrorInfo { class WriteErrorInfo {
constructor( constructor(
public readonly error: StorageError, public readonly error: StorageError,
public readonly refItem: LogItem | undefined, public readonly refItem: ILogItem | undefined,
public readonly operationName: string, public readonly operationName: string,
public readonly keys: IDBKey[] | undefined, public readonly keys: IDBKey[] | undefined,
) {} ) {}
@ -169,7 +169,7 @@ export class Transaction {
return this._store(StoreNames.accountData, idbStore => new AccountDataStore(idbStore)); return this._store(StoreNames.accountData, idbStore => new AccountDataStore(idbStore));
} }
async complete(log?: LogItem): Promise<void> { async complete(log?: ILogItem): Promise<void> {
try { try {
await txnAsPromise(this._txn); await txnAsPromise(this._txn);
} catch (err) { } catch (err) {
@ -190,7 +190,7 @@ export class Transaction {
return error; return error;
} }
abort(log?: LogItem): void { abort(log?: ILogItem): void {
// TODO: should we wrap the exception in a StorageError? // TODO: should we wrap the exception in a StorageError?
try { try {
this._txn.abort(); this._txn.abort();
@ -202,14 +202,14 @@ export class Transaction {
} }
} }
addWriteError(error: StorageError, refItem: LogItem | undefined, operationName: string, keys: IDBKey[] | undefined) { addWriteError(error: StorageError, refItem: ILogItem | undefined, operationName: string, keys: IDBKey[] | undefined) {
// don't log subsequent `AbortError`s // don't log subsequent `AbortError`s
if (error.errcode !== "AbortError" || this._writeErrors.length === 0) { if (error.errcode !== "AbortError" || this._writeErrors.length === 0) {
this._writeErrors.push(new WriteErrorInfo(error, refItem, operationName, keys)); this._writeErrors.push(new WriteErrorInfo(error, refItem, operationName, keys));
} }
} }
private _logWriteErrors(parentItem: LogItem | undefined) { private _logWriteErrors(parentItem: ILogItem | undefined) {
const callback = errorGroupItem => { const callback = errorGroupItem => {
// we don't have context when there is no parentItem, so at least log stores // we don't have context when there is no parentItem, so at least log stores
if (!parentItem) { if (!parentItem) {
@ -226,7 +226,7 @@ export class Transaction {
}; };
const label = `${this._writeErrors.length} storage write operation(s) failed`; const label = `${this._writeErrors.length} storage write operation(s) failed`;
if (parentItem) { if (parentItem) {
parentItem.wrap(label, callback); parentItem.wrap(label, callback, null, null);
} else { } else {
this.logger.run(label, callback); this.logger.run(label, callback);
} }

View file

@ -11,10 +11,10 @@ import {SessionStore} from "./stores/SessionStore";
import {Store} from "./Store"; import {Store} from "./Store";
import {encodeScopeTypeKey} from "./stores/OperationStore"; import {encodeScopeTypeKey} from "./stores/OperationStore";
import {MAX_UNICODE} from "./stores/common"; import {MAX_UNICODE} from "./stores/common";
import {LogItem} from "../../../logging/LogItem"; import {ILogItem} from "../../../logging/LogItem";
export type MigrationFunc = (db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: LogItem) => Promise<void> | void; export type MigrationFunc = (db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem) => Promise<void> | void;
// FUNCTIONS SHOULD ONLY BE APPENDED!! // FUNCTIONS SHOULD ONLY BE APPENDED!!
// the index in the array is the database version // the index in the array is the database version
export const schema: MigrationFunc[] = [ export const schema: MigrationFunc[] = [
@ -166,7 +166,7 @@ function createTimelineRelationsStore(db: IDBDatabase) : void {
} }
//v11 doesn't change the schema, but ensures all userIdentities have all the roomIds they should (see #470) //v11 doesn't change the schema, but ensures all userIdentities have all the roomIds they should (see #470)
async function fixMissingRoomsInUserIdentities(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: LogItem) { async function fixMissingRoomsInUserIdentities(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem) {
const roomSummaryStore = txn.objectStore("roomSummary"); const roomSummaryStore = txn.objectStore("roomSummary");
const trackedRoomIds: string[] = []; const trackedRoomIds: string[] = [];
await iterateCursor<SummaryData>(roomSummaryStore.openCursor(), roomSummary => { await iterateCursor<SummaryData>(roomSummaryStore.openCursor(), roomSummary => {
@ -196,7 +196,7 @@ async function fixMissingRoomsInUserIdentities(db: IDBDatabase, txn: IDBTransact
const updatedIdentity = addRoomToIdentity(identity, userId, roomId); const updatedIdentity = addRoomToIdentity(identity, userId, roomId);
if (updatedIdentity) { if (updatedIdentity) {
log.log({l: `fixing up`, id: userId, log.log({l: `fixing up`, id: userId,
roomsBefore: originalRoomCount, roomsAfter: updatedIdentity.roomIds.length}); roomsBefore: originalRoomCount, roomsAfter: updatedIdentity.roomIds.length}, null);
userIdentitiesStore.put(updatedIdentity); userIdentitiesStore.put(updatedIdentity);
foundMissing = true; foundMissing = true;
} }
@ -207,7 +207,7 @@ async function fixMissingRoomsInUserIdentities(db: IDBDatabase, txn: IDBTransact
// so we'll create a new one on the next message that will be properly shared // so we'll create a new one on the next message that will be properly shared
outboundGroupSessionsStore.delete(roomId); outboundGroupSessionsStore.delete(roomId);
} }
}); }, null, null);
} }
} }
@ -220,7 +220,7 @@ async function changeSSSSKeyPrefix(db: IDBDatabase, txn: IDBTransaction) {
} }
} }
// v13 // v13
async function backupAndRestoreE2EEAccountToLocalStorage(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: LogItem) { async function backupAndRestoreE2EEAccountToLocalStorage(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem) {
const session = txn.objectStore("session"); const session = txn.objectStore("session");
// the Store object gets passed in several things through the Transaction class (a wrapper around IDBTransaction), // the Store object gets passed in several things through the Transaction class (a wrapper around IDBTransaction),
// the only thing we should need here is the databaseName though, so we mock it out. // the only thing we should need here is the databaseName though, so we mock it out.

View file

@ -16,7 +16,7 @@ limitations under the License.
import {Store} from "../Store"; import {Store} from "../Store";
import {IDOMStorage} from "../types"; import {IDOMStorage} from "../types";
import {SESSION_E2EE_KEY_PREFIX} from "../../../e2ee/common.js"; import {SESSION_E2EE_KEY_PREFIX} from "../../../e2ee/common.js";
import {LogItem} from "../../../../logging/LogItem"; import {ILogItem} from "../../../../logging/LogItem";
import {parse, stringify} from "../../../../utils/typedJSON"; import {parse, stringify} from "../../../../utils/typedJSON";
export interface SessionEntry { export interface SessionEntry {
@ -64,7 +64,7 @@ export class SessionStore {
}); });
} }
async tryRestoreE2EEIdentityFromLocalStorage(log: LogItem): Promise<boolean> { async tryRestoreE2EEIdentityFromLocalStorage(log: ILogItem): Promise<boolean> {
let success = false; let success = false;
const lsPrefix = this._localStorageKeyPrefix; const lsPrefix = this._localStorageKeyPrefix;
const prefix = lsPrefix + SESSION_E2EE_KEY_PREFIX; const prefix = lsPrefix + SESSION_E2EE_KEY_PREFIX;

View file

@ -20,7 +20,7 @@ import { encodeUint32, decodeUint32 } from "../utils";
import {KeyLimits} from "../../common"; import {KeyLimits} from "../../common";
import {Store} from "../Store"; import {Store} from "../Store";
import {TimelineEvent, StateEvent} from "../../types"; import {TimelineEvent, StateEvent} from "../../types";
import {LogItem} from "../../../../logging/LogItem"; import {ILogItem} from "../../../../logging/LogItem";
interface Annotation { interface Annotation {
count: number; count: number;
@ -286,7 +286,7 @@ export class TimelineEventStore {
* *
* Returns if the event was not yet known and the entry was written. * Returns if the event was not yet known and the entry was written.
*/ */
tryInsert(entry: TimelineEventEntry, log: LogItem): Promise<boolean> { tryInsert(entry: TimelineEventEntry, log: ILogItem): Promise<boolean> {
(entry as TimelineEventStorageEntry).key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex); (entry as TimelineEventStorageEntry).key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex);
(entry as TimelineEventStorageEntry).eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id); (entry as TimelineEventStorageEntry).eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id);
return this._timelineStore.tryAdd(entry as TimelineEventStorageEntry, log); return this._timelineStore.tryAdd(entry as TimelineEventStorageEntry, log);
@ -320,7 +320,7 @@ export class TimelineEventStore {
import {createMockStorage} from "../../../../mocks/Storage"; import {createMockStorage} from "../../../../mocks/Storage";
import {createEvent, withTextBody} from "../../../../mocks/event.js"; import {createEvent, withTextBody} from "../../../../mocks/event.js";
import {createEventEntry} from "../../../room/timeline/persistence/common.js"; import {createEventEntry} from "../../../room/timeline/persistence/common.js";
import {Instance as logItem} from "../../../../logging/NullLogger.js"; import {Instance as nullLogger} from "../../../../logging/NullLogger";
export function tests() { export function tests() {
@ -368,7 +368,7 @@ export function tests() {
let eventKey = EventKey.defaultFragmentKey(109); let eventKey = EventKey.defaultFragmentKey(109);
for (const insertedId of insertedIds) { for (const insertedId of insertedIds) {
const entry = createEventEntry(eventKey.nextKey(), roomId, createEventWithId(insertedId)); const entry = createEventEntry(eventKey.nextKey(), roomId, createEventWithId(insertedId));
assert(await txn.timelineEvents.tryInsert(entry, logItem)); assert(await txn.timelineEvents.tryInsert(entry, nullLogger.item));
eventKey = eventKey.nextKey(); eventKey = eventKey.nextKey();
} }
const eventKeyMap = await txn.timelineEvents.getEventKeysForIds(roomId, checkedIds); const eventKeyMap = await txn.timelineEvents.getEventKeysForIds(roomId, checkedIds);

View file

@ -18,7 +18,7 @@ import {FDBFactory, FDBKeyRange} from "../../lib/fake-indexeddb/index.js";
import {StorageFactory} from "../matrix/storage/idb/StorageFactory"; import {StorageFactory} from "../matrix/storage/idb/StorageFactory";
import {IDOMStorage} from "../matrix/storage/idb/types"; import {IDOMStorage} from "../matrix/storage/idb/types";
import {Storage} from "../matrix/storage/idb/Storage"; import {Storage} from "../matrix/storage/idb/Storage";
import {Instance as nullLogger} from "../logging/NullLogger.js"; import {Instance as nullLogger} from "../logging/NullLogger";
import {openDatabase, CreateObjectStore} from "../matrix/storage/idb/utils"; import {openDatabase, CreateObjectStore} from "../matrix/storage/idb/utils";
export function createMockStorage(): Promise<Storage> { export function createMockStorage(): Promise<Storage> {