From 348a9c83f5b96798dadce02fbc7b1e5437163409 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 18:51:02 -0700 Subject: [PATCH 001/174] Rename ObservableArray to TypeScript --- src/domain/session/room/timeline/TilesCollection.js | 2 +- src/observable/index.js | 2 +- src/observable/list/AsyncMappedList.js | 2 +- src/observable/list/ConcatList.js | 2 +- src/observable/list/MappedList.js | 2 +- src/observable/list/{ObservableArray.js => ObservableArray.ts} | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename src/observable/list/{ObservableArray.js => ObservableArray.ts} (100%) diff --git a/src/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js index b3696fad..d83dc5bd 100644 --- a/src/domain/session/room/timeline/TilesCollection.js +++ b/src/domain/session/room/timeline/TilesCollection.js @@ -253,7 +253,7 @@ export class TilesCollection extends BaseObservableList { } } -import {ObservableArray} from "../../../../observable/list/ObservableArray.js"; +import {ObservableArray} from "../../../../observable/list/ObservableArray"; import {UpdateAction} from "./UpdateAction.js"; export function tests() { diff --git a/src/observable/index.js b/src/observable/index.js index 47b68e91..27d8a7ef 100644 --- a/src/observable/index.js +++ b/src/observable/index.js @@ -20,7 +20,7 @@ import {MappedMap} from "./map/MappedMap.js"; import {JoinedMap} from "./map/JoinedMap.js"; import {BaseObservableMap} from "./map/BaseObservableMap.js"; // re-export "root" (of chain) collections -export { ObservableArray } from "./list/ObservableArray.js"; +export { ObservableArray } from "./list/ObservableArray"; export { SortedArray } from "./list/SortedArray.js"; export { MappedList } from "./list/MappedList.js"; export { AsyncMappedList } from "./list/AsyncMappedList.js"; diff --git a/src/observable/list/AsyncMappedList.js b/src/observable/list/AsyncMappedList.js index 9af1f95a..9d6e2dd6 100644 --- a/src/observable/list/AsyncMappedList.js +++ b/src/observable/list/AsyncMappedList.js @@ -143,7 +143,7 @@ class ResetEvent { } } -import {ObservableArray} from "./ObservableArray.js"; +import {ObservableArray} from "./ObservableArray"; import {ListObserver} from "../../mocks/ListObserver.js"; export function tests() { diff --git a/src/observable/list/ConcatList.js b/src/observable/list/ConcatList.js index e87ccb60..69ce5efd 100644 --- a/src/observable/list/ConcatList.js +++ b/src/observable/list/ConcatList.js @@ -104,7 +104,7 @@ export class ConcatList extends BaseObservableList { } } -import {ObservableArray} from "./ObservableArray.js"; +import {ObservableArray} from "./ObservableArray"; export async function tests() { return { test_length(assert) { diff --git a/src/observable/list/MappedList.js b/src/observable/list/MappedList.js index c799fcff..39a2cf0d 100644 --- a/src/observable/list/MappedList.js +++ b/src/observable/list/MappedList.js @@ -56,7 +56,7 @@ export class MappedList extends BaseMappedList { } } -import {ObservableArray} from "./ObservableArray.js"; +import {ObservableArray} from "./ObservableArray"; import {BaseObservableList} from "./BaseObservableList"; export async function tests() { diff --git a/src/observable/list/ObservableArray.js b/src/observable/list/ObservableArray.ts similarity index 100% rename from src/observable/list/ObservableArray.js rename to src/observable/list/ObservableArray.ts From b148f3ca9e4537394a46d7e74638b2a93faf0077 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 18:55:31 -0700 Subject: [PATCH 002/174] Add type annotations to ObservableArray --- src/observable/list/ObservableArray.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/observable/list/ObservableArray.ts b/src/observable/list/ObservableArray.ts index 5d9f5c12..718ac99f 100644 --- a/src/observable/list/ObservableArray.ts +++ b/src/observable/list/ObservableArray.ts @@ -16,52 +16,54 @@ limitations under the License. import {BaseObservableList} from "./BaseObservableList"; -export class ObservableArray extends BaseObservableList { - constructor(initialValues = []) { +export class ObservableArray extends BaseObservableList { + private _items: T[]; + + constructor(initialValues: T[] = []) { super(); this._items = initialValues; } - append(item) { + append(item: T): void { this._items.push(item); this.emitAdd(this._items.length - 1, item); } - remove(idx) { + remove(idx: number): void { const [item] = this._items.splice(idx, 1); this.emitRemove(idx, item); } - insertMany(idx, items) { + insertMany(idx: number, items: T[]): void { for(let item of items) { this.insert(idx, item); idx += 1; } } - insert(idx, item) { + insert(idx: number, item: T): void { this._items.splice(idx, 0, item); this.emitAdd(idx, item); } - update(idx, item, params = null) { + update(idx: number, item: T, params: any = null): void { if (idx < this._items.length) { this._items[idx] = item; this.emitUpdate(idx, item, params); } } - get array() { + get array(): Readonly { return this._items; } - at(idx) { + at(idx: number): T | undefined { if (this._items && idx >= 0 && idx < this._items.length) { return this._items[idx]; } } - get length() { + get length(): number { return this._items.length; } From e6de873b6e8c2822910007a67ca93f089209b6c1 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 18:56:28 -0700 Subject: [PATCH 003/174] Rename AsyncMappedList to TypeScript --- src/observable/index.js | 2 +- src/observable/list/{AsyncMappedList.js => AsyncMappedList.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/observable/list/{AsyncMappedList.js => AsyncMappedList.ts} (100%) diff --git a/src/observable/index.js b/src/observable/index.js index 27d8a7ef..8b2e5367 100644 --- a/src/observable/index.js +++ b/src/observable/index.js @@ -23,7 +23,7 @@ import {BaseObservableMap} from "./map/BaseObservableMap.js"; export { ObservableArray } from "./list/ObservableArray"; export { SortedArray } from "./list/SortedArray.js"; export { MappedList } from "./list/MappedList.js"; -export { AsyncMappedList } from "./list/AsyncMappedList.js"; +export { AsyncMappedList } from "./list/AsyncMappedList"; export { ConcatList } from "./list/ConcatList.js"; export { ObservableMap } from "./map/ObservableMap.js"; diff --git a/src/observable/list/AsyncMappedList.js b/src/observable/list/AsyncMappedList.ts similarity index 100% rename from src/observable/list/AsyncMappedList.js rename to src/observable/list/AsyncMappedList.ts From 0e6c59983f8758f6238ed1a6457e29e1e4d284f7 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:11:19 -0700 Subject: [PATCH 004/174] Generalize BaseMappedList to allow mappers to promises --- src/observable/list/BaseMappedList.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/observable/list/BaseMappedList.ts b/src/observable/list/BaseMappedList.ts index f2203d34..b5a669d1 100644 --- a/src/observable/list/BaseMappedList.ts +++ b/src/observable/list/BaseMappedList.ts @@ -21,15 +21,15 @@ import {findAndUpdateInArray} from "./common"; export type Mapper = (value: F) => T export type Updater = (mappedValue: T, params: any, value: F) => void; -export class BaseMappedList extends BaseObservableList { +export class BaseMappedList extends BaseObservableList { protected _sourceList: BaseObservableList; protected _sourceUnsubscribe: (() => void) | null = null; - _mapper: Mapper; + _mapper: Mapper; _updater: Updater; _removeCallback?: (value: T) => void; _mappedValues: T[] | null = null; - constructor(sourceList: BaseObservableList, mapper: Mapper, updater: Updater, removeCallback?: (value: T) => void) { + constructor(sourceList: BaseObservableList, mapper: Mapper, updater: Updater, removeCallback?: (value: T) => void) { super(); this._sourceList = sourceList; this._mapper = mapper; @@ -50,12 +50,12 @@ export class BaseMappedList extends BaseObservableList { } } -export function runAdd(list: BaseMappedList, index: number, mappedValue: T): void { +export function runAdd(list: BaseMappedList, index: number, mappedValue: T): void { list._mappedValues!.splice(index, 0, mappedValue); list.emitAdd(index, mappedValue); } -export function runUpdate(list: BaseMappedList, index: number, value: F, params: any): void { +export function runUpdate(list: BaseMappedList, index: number, value: F, params: any): void { const mappedValue = list._mappedValues![index]; if (list._updater) { list._updater(mappedValue, params, value); @@ -63,7 +63,7 @@ export function runUpdate(list: BaseMappedList, index: number, value: list.emitUpdate(index, mappedValue, params); } -export function runRemove(list: BaseMappedList, index: number): void { +export function runRemove(list: BaseMappedList, index: number): void { const mappedValue = list._mappedValues![index]; list._mappedValues!.splice(index, 1); if (list._removeCallback) { @@ -72,14 +72,14 @@ export function runRemove(list: BaseMappedList, index: number): void { list.emitRemove(index, mappedValue); } -export function runMove(list: BaseMappedList, fromIdx: number, toIdx: number): void { +export function runMove(list: BaseMappedList, fromIdx: number, toIdx: number): void { const mappedValue = list._mappedValues![fromIdx]; list._mappedValues!.splice(fromIdx, 1); list._mappedValues!.splice(toIdx, 0, mappedValue); list.emitMove(fromIdx, toIdx, mappedValue); } -export function runReset(list: BaseMappedList): void { +export function runReset(list: BaseMappedList): void { list._mappedValues = []; list.emitReset(); } From 8466a910da48d329f8b6a0fff085136763884027 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:13:38 -0700 Subject: [PATCH 005/174] Add type annotations to AsyncMappedList --- src/observable/list/AsyncMappedList.ts | 79 +++++++++++--------------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/src/observable/list/AsyncMappedList.ts b/src/observable/list/AsyncMappedList.ts index 9d6e2dd6..0a919cdc 100644 --- a/src/observable/list/AsyncMappedList.ts +++ b/src/observable/list/AsyncMappedList.ts @@ -15,15 +15,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {BaseMappedList, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList"; +import {IListObserver} from "./BaseObservableList"; +import {BaseMappedList, Mapper, Updater, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList"; -export class AsyncMappedList extends BaseMappedList { - constructor(sourceList, mapper, updater, removeCallback) { - super(sourceList, mapper, updater, removeCallback); - this._eventQueue = null; - } +export class AsyncMappedList extends BaseMappedList> implements IListObserver { + private _eventQueue: AsyncEvent[] | null = null; + private _flushing: boolean = false; - onSubscribeFirst() { + onSubscribeFirst(): void { this._sourceUnsubscribe = this._sourceList.subscribe(this); this._eventQueue = []; this._mappedValues = []; @@ -35,110 +34,100 @@ export class AsyncMappedList extends BaseMappedList { this._flush(); } - async _flush() { + async _flush(): Promise { if (this._flushing) { return; } this._flushing = true; try { - while (this._eventQueue.length) { - const event = this._eventQueue.shift(); - await event.run(this); + while (this._eventQueue!.length) { + const event = this._eventQueue!.shift(); + await event!.run(this); } } finally { this._flushing = false; } } - onReset() { + onReset(): void { if (this._eventQueue) { this._eventQueue.push(new ResetEvent()); this._flush(); } } - onAdd(index, value) { + onAdd(index: number, value: F): void { if (this._eventQueue) { this._eventQueue.push(new AddEvent(index, value)); this._flush(); } } - onUpdate(index, value, params) { + onUpdate(index: number, value: F, params: any): void { if (this._eventQueue) { this._eventQueue.push(new UpdateEvent(index, value, params)); this._flush(); } } - onRemove(index) { + onRemove(index: number): void { if (this._eventQueue) { this._eventQueue.push(new RemoveEvent(index)); this._flush(); } } - onMove(fromIdx, toIdx) { + onMove(fromIdx: number, toIdx: number): void { if (this._eventQueue) { this._eventQueue.push(new MoveEvent(fromIdx, toIdx)); this._flush(); } } - onUnsubscribeLast() { - this._sourceUnsubscribe(); + onUnsubscribeLast(): void { + this._sourceUnsubscribe!(); this._eventQueue = null; this._mappedValues = null; } } -class AddEvent { - constructor(index, value) { - this.index = index; - this.value = value; - } +type AsyncEvent = AddEvent | UpdateEvent | RemoveEvent | MoveEvent | ResetEvent - async run(list) { +class AddEvent { + constructor(public index: number, public value: F) {} + + async run(list: AsyncMappedList): Promise { const mappedValue = await list._mapper(this.value); runAdd(list, this.index, mappedValue); } } -class UpdateEvent { - constructor(index, value, params) { - this.index = index; - this.value = value; - this.params = params; - } +class UpdateEvent { + constructor(public index: number, public value: F, public params: any) {} - async run(list) { + async run(list: AsyncMappedList): Promise { runUpdate(list, this.index, this.value, this.params); } } -class RemoveEvent { - constructor(index) { - this.index = index; - } +class RemoveEvent { + constructor(public index: number) {} - async run(list) { + async run(list: AsyncMappedList): Promise { runRemove(list, this.index); } } -class MoveEvent { - constructor(fromIdx, toIdx) { - this.fromIdx = fromIdx; - this.toIdx = toIdx; - } +class MoveEvent { + constructor(public fromIdx: number, public toIdx: number) {} - async run(list) { + async run(list: AsyncMappedList): Promise { runMove(list, this.fromIdx, this.toIdx); } } -class ResetEvent { - async run(list) { +class ResetEvent { + async run(list: AsyncMappedList): Promise { runReset(list); } } @@ -150,7 +139,7 @@ export function tests() { return { "events are emitted in order": async assert => { const double = n => n * n; - const source = new ObservableArray(); + const source = new ObservableArray(); const mapper = new AsyncMappedList(source, async n => { await new Promise(r => setTimeout(r, n)); return {n: double(n)}; From ddca467e304cc4965c7f76446e391960c532d91e Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:15:55 -0700 Subject: [PATCH 006/174] Rename ConcatList to TypeScript --- src/observable/index.js | 2 +- src/observable/list/{ConcatList.js => ConcatList.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/observable/list/{ConcatList.js => ConcatList.ts} (100%) diff --git a/src/observable/index.js b/src/observable/index.js index 8b2e5367..9784444b 100644 --- a/src/observable/index.js +++ b/src/observable/index.js @@ -24,7 +24,7 @@ export { ObservableArray } from "./list/ObservableArray"; export { SortedArray } from "./list/SortedArray.js"; export { MappedList } from "./list/MappedList.js"; export { AsyncMappedList } from "./list/AsyncMappedList"; -export { ConcatList } from "./list/ConcatList.js"; +export { ConcatList } from "./list/ConcatList"; export { ObservableMap } from "./map/ObservableMap.js"; // avoid circular dependency between these classes diff --git a/src/observable/list/ConcatList.js b/src/observable/list/ConcatList.ts similarity index 100% rename from src/observable/list/ConcatList.js rename to src/observable/list/ConcatList.ts From 588da9b7193eb5ce04ab2e2622df4e1b8230602d Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:25:47 -0700 Subject: [PATCH 007/174] Relax types on BaseObservableList and add helper for tests --- src/observable/list/BaseObservableList.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/observable/list/BaseObservableList.ts b/src/observable/list/BaseObservableList.ts index 9ba6559a..6d8bd1af 100644 --- a/src/observable/list/BaseObservableList.ts +++ b/src/observable/list/BaseObservableList.ts @@ -24,6 +24,17 @@ export interface IListObserver { onMove(from: number, to: number, value: T, list: BaseObservableList): void } +export function defaultObserverWith(overrides: { [key in keyof IListObserver]?: IListObserver[key] }): IListObserver { + const defaults = { + onReset(){}, + onAdd(){}, + onUpdate(){}, + onRemove(){}, + onMove(){}, + } + return Object.assign(defaults, overrides); +} + export abstract class BaseObservableList extends BaseObservable> { emitReset() { for(let h of this._handlers) { @@ -38,7 +49,7 @@ export abstract class BaseObservableList extends BaseObservable extends BaseObservable; + abstract [Symbol.iterator](); abstract get length(): number; } From 3b131f2db69e2f4e95530266078e6744a038c016 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:28:00 -0700 Subject: [PATCH 008/174] Add type annotations to ConcatList --- src/observable/list/ConcatList.ts | 39 +++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/observable/list/ConcatList.ts b/src/observable/list/ConcatList.ts index 69ce5efd..5822468a 100644 --- a/src/observable/list/ConcatList.ts +++ b/src/observable/list/ConcatList.ts @@ -14,16 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {BaseObservableList} from "./BaseObservableList"; +import {BaseObservableList, IListObserver} from "./BaseObservableList"; -export class ConcatList extends BaseObservableList { - constructor(...sourceLists) { +export class ConcatList extends BaseObservableList implements IListObserver { + protected _sourceLists: BaseObservableList[]; + protected _sourceUnsubscribes: (() => void)[] | null = null; + + constructor(...sourceLists: BaseObservableList[]) { super(); this._sourceLists = sourceLists; - this._sourceUnsubscribes = null; } - _offsetForSource(sourceList) { + _offsetForSource(sourceList: BaseObservableList): number { const listIdx = this._sourceLists.indexOf(sourceList); let offset = 0; for (let i = 0; i < listIdx; ++i) { @@ -32,17 +34,17 @@ export class ConcatList extends BaseObservableList { return offset; } - onSubscribeFirst() { + onSubscribeFirst(): void { this._sourceUnsubscribes = this._sourceLists.map(sourceList => sourceList.subscribe(this)); } - onUnsubscribeLast() { - for (const sourceUnsubscribe of this._sourceUnsubscribes) { + onUnsubscribeLast(): void { + for (const sourceUnsubscribe of this._sourceUnsubscribes!) { sourceUnsubscribe(); } } - onReset() { + onReset(): void { // TODO: not ideal if other source lists are large // but working impl for now // reset, and @@ -54,11 +56,11 @@ export class ConcatList extends BaseObservableList { } } - onAdd(index, value, sourceList) { + onAdd(index: number, value: T, sourceList: BaseObservableList): void { this.emitAdd(this._offsetForSource(sourceList) + index, value); } - onUpdate(index, value, params, sourceList) { + onUpdate(index: number, value: T, params: any, sourceList: BaseObservableList): void { // if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it // as we are not supposed to call `length` on any uninitialized list if (!this._sourceUnsubscribes) { @@ -67,16 +69,16 @@ export class ConcatList extends BaseObservableList { this.emitUpdate(this._offsetForSource(sourceList) + index, value, params); } - onRemove(index, value, sourceList) { + onRemove(index: number, value: T, sourceList: BaseObservableList): void { this.emitRemove(this._offsetForSource(sourceList) + index, value); } - onMove(fromIdx, toIdx, value, sourceList) { + onMove(fromIdx: number, toIdx: number, value: T, sourceList: BaseObservableList): void { const offset = this._offsetForSource(sourceList); this.emitMove(offset + fromIdx, offset + toIdx, value); } - get length() { + get length(): number { let len = 0; for (let i = 0; i < this._sourceLists.length; ++i) { len += this._sourceLists[i].length; @@ -105,6 +107,7 @@ export class ConcatList extends BaseObservableList { } import {ObservableArray} from "./ObservableArray"; +import {defaultObserverWith} from "./BaseObservableList"; export async function tests() { return { test_length(assert) { @@ -133,13 +136,13 @@ export async function tests() { const list2 = new ObservableArray([11, 12, 13]); const all = new ConcatList(list1, list2); let fired = false; - all.subscribe({ + all.subscribe(defaultObserverWith({ onAdd(index, value) { fired = true; assert.equal(index, 4); assert.equal(value, 11.5); } - }); + })); list2.insert(1, 11.5); assert(fired); }, @@ -148,13 +151,13 @@ export async function tests() { const list2 = new ObservableArray([11, 12, 13]); const all = new ConcatList(list1, list2); let fired = false; - all.subscribe({ + all.subscribe(defaultObserverWith({ onUpdate(index, value) { fired = true; assert.equal(index, 4); assert.equal(value, 10); } - }); + })); list2.emitUpdate(1, 10); assert(fired); }, From 0466b4952080d3db5ad5b1529fc50602df2cbc0b Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:31:00 -0700 Subject: [PATCH 009/174] Rename MappedList to TypeScript --- src/domain/session/room/timeline/ReactionsViewModel.js | 2 +- src/observable/index.js | 2 +- src/observable/list/{MappedList.js => MappedList.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/observable/list/{MappedList.js => MappedList.ts} (100%) diff --git a/src/domain/session/room/timeline/ReactionsViewModel.js b/src/domain/session/room/timeline/ReactionsViewModel.js index 25d74b49..c049dd11 100644 --- a/src/domain/session/room/timeline/ReactionsViewModel.js +++ b/src/domain/session/room/timeline/ReactionsViewModel.js @@ -188,7 +188,7 @@ import {NullLogItem, NullLogger} from "../../../../logging/NullLogger.js"; import {HomeServer as MockHomeServer} from "../../../../mocks/HomeServer.js"; // other imports import {BaseMessageTile} from "./tiles/BaseMessageTile.js"; -import {MappedList} from "../../../../observable/list/MappedList.js"; +import {MappedList} from "../../../../observable/list/MappedList"; import {ObservableValue} from "../../../../observable/ObservableValue"; import {PowerLevels} from "../../../../matrix/room/PowerLevels.js"; diff --git a/src/observable/index.js b/src/observable/index.js index 9784444b..35ecaffc 100644 --- a/src/observable/index.js +++ b/src/observable/index.js @@ -22,7 +22,7 @@ import {BaseObservableMap} from "./map/BaseObservableMap.js"; // re-export "root" (of chain) collections export { ObservableArray } from "./list/ObservableArray"; export { SortedArray } from "./list/SortedArray.js"; -export { MappedList } from "./list/MappedList.js"; +export { MappedList } from "./list/MappedList"; export { AsyncMappedList } from "./list/AsyncMappedList"; export { ConcatList } from "./list/ConcatList"; export { ObservableMap } from "./map/ObservableMap.js"; diff --git a/src/observable/list/MappedList.js b/src/observable/list/MappedList.ts similarity index 100% rename from src/observable/list/MappedList.js rename to src/observable/list/MappedList.ts From 84187ce10957f0336d116ee4fe2e55d95f4af745 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:40:21 -0700 Subject: [PATCH 010/174] Make updater optional in BaseObservableList --- src/observable/list/BaseMappedList.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/observable/list/BaseMappedList.ts b/src/observable/list/BaseMappedList.ts index b5a669d1..4e3d05e0 100644 --- a/src/observable/list/BaseMappedList.ts +++ b/src/observable/list/BaseMappedList.ts @@ -25,11 +25,11 @@ export class BaseMappedList extends BaseObservableList { protected _sourceList: BaseObservableList; protected _sourceUnsubscribe: (() => void) | null = null; _mapper: Mapper; - _updater: Updater; + _updater?: Updater; _removeCallback?: (value: T) => void; _mappedValues: T[] | null = null; - constructor(sourceList: BaseObservableList, mapper: Mapper, updater: Updater, removeCallback?: (value: T) => void) { + constructor(sourceList: BaseObservableList, mapper: Mapper, updater?: Updater, removeCallback?: (value: T) => void) { super(); this._sourceList = sourceList; this._mapper = mapper; From 1363af24a7d5010e86db9bb4ae586f5f68f7f6bf Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:40:48 -0700 Subject: [PATCH 011/174] Add type annotations to MappedList --- src/observable/list/MappedList.ts | 42 ++++++++++++++++--------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/observable/list/MappedList.ts b/src/observable/list/MappedList.ts index 39a2cf0d..ebb418d3 100644 --- a/src/observable/list/MappedList.ts +++ b/src/observable/list/MappedList.ts @@ -15,9 +15,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {IListObserver} from "./BaseObservableList"; import {BaseMappedList, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList"; -export class MappedList extends BaseMappedList { +export class MappedList extends BaseMappedList implements IListObserver { onSubscribeFirst() { this._sourceUnsubscribe = this._sourceList.subscribe(this); this._mappedValues = []; @@ -26,16 +27,16 @@ export class MappedList extends BaseMappedList { } } - onReset() { + onReset(): void { runReset(this); } - onAdd(index, value) { + onAdd(index: number, value: F): void { const mappedValue = this._mapper(value); runAdd(this, index, mappedValue); } - onUpdate(index, value, params) { + onUpdate(index: number, value: F, params: any): void { // if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it if (!this._mappedValues) { return; @@ -43,24 +44,25 @@ export class MappedList extends BaseMappedList { runUpdate(this, index, value, params); } - onRemove(index) { + onRemove(index: number): void { runRemove(this, index); } - onMove(fromIdx, toIdx) { + onMove(fromIdx: number, toIdx: number): void { runMove(this, fromIdx, toIdx); } - onUnsubscribeLast() { - this._sourceUnsubscribe(); + onUnsubscribeLast(): void { + this._sourceUnsubscribe!(); } } import {ObservableArray} from "./ObservableArray"; import {BaseObservableList} from "./BaseObservableList"; +import {defaultObserverWith} from "./BaseObservableList"; export async function tests() { - class MockList extends BaseObservableList { + class MockList extends BaseObservableList { get length() { return 0; } @@ -74,26 +76,26 @@ export async function tests() { const source = new MockList(); const mapped = new MappedList(source, n => {return {n: n*n};}); let fired = false; - const unsubscribe = mapped.subscribe({ + const unsubscribe = mapped.subscribe(defaultObserverWith({ onAdd(idx, value) { fired = true; assert.equal(idx, 0); assert.equal(value.n, 36); } - }); + })); source.emitAdd(0, 6); assert(fired); unsubscribe(); }, test_update(assert) { const source = new MockList(); - const mapped = new MappedList( + const mapped = new MappedList( source, n => {return {n: n*n};}, (o, p, n) => o.m = n*n ); let fired = false; - const unsubscribe = mapped.subscribe({ + const unsubscribe = mapped.subscribe(defaultObserverWith({ onAdd() {}, onUpdate(idx, value) { fired = true; @@ -101,7 +103,7 @@ export async function tests() { assert.equal(value.n, 36); assert.equal(value.m, 49); } - }); + })); source.emitAdd(0, 6); source.emitUpdate(0, 7); assert(fired); @@ -113,9 +115,9 @@ export async function tests() { source, n => {return n*n;} ); - mapped.subscribe({ + mapped.subscribe(defaultObserverWith({ onUpdate() { assert.fail(); } - }); + })); assert.equal(mapped.findAndUpdate( n => n === 100, () => assert.fail() @@ -127,9 +129,9 @@ export async function tests() { source, n => {return n*n;} ); - mapped.subscribe({ + mapped.subscribe(defaultObserverWith({ onUpdate() { assert.fail(); } - }); + })); let fired = false; assert.equal(mapped.findAndUpdate( n => n === 9, @@ -148,14 +150,14 @@ export async function tests() { n => {return n*n;} ); let fired = false; - mapped.subscribe({ + mapped.subscribe(defaultObserverWith({ onUpdate(idx, n, params) { assert.equal(idx, 1); assert.equal(n, 9); assert.equal(params, "param"); fired = true; } - }); + })); assert.equal(mapped.findAndUpdate(n => n === 9, () => "param"), true); assert.equal(fired, true); }, From 7b2e452cd5c61fb6265fcb16820423162e27a0cf Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:43:48 -0700 Subject: [PATCH 012/174] Rename SortedArray to TypeScript --- src/matrix/room/sending/SendQueue.js | 2 +- src/observable/index.js | 2 +- src/observable/list/{SortedArray.js => SortedArray.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/observable/list/{SortedArray.js => SortedArray.ts} (100%) diff --git a/src/matrix/room/sending/SendQueue.js b/src/matrix/room/sending/SendQueue.js index 28408e34..7ba48b15 100644 --- a/src/matrix/room/sending/SendQueue.js +++ b/src/matrix/room/sending/SendQueue.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {SortedArray} from "../../../observable/list/SortedArray.js"; +import {SortedArray} from "../../../observable/list/SortedArray"; import {ConnectionError} from "../../error.js"; import {PendingEvent, SendStatus} from "./PendingEvent.js"; import {makeTxnId, isTxnId} from "../../common.js"; diff --git a/src/observable/index.js b/src/observable/index.js index 35ecaffc..4d7f18a3 100644 --- a/src/observable/index.js +++ b/src/observable/index.js @@ -21,7 +21,7 @@ import {JoinedMap} from "./map/JoinedMap.js"; import {BaseObservableMap} from "./map/BaseObservableMap.js"; // re-export "root" (of chain) collections export { ObservableArray } from "./list/ObservableArray"; -export { SortedArray } from "./list/SortedArray.js"; +export { SortedArray } from "./list/SortedArray"; export { MappedList } from "./list/MappedList"; export { AsyncMappedList } from "./list/AsyncMappedList"; export { ConcatList } from "./list/ConcatList"; diff --git a/src/observable/list/SortedArray.js b/src/observable/list/SortedArray.ts similarity index 100% rename from src/observable/list/SortedArray.js rename to src/observable/list/SortedArray.ts From 3d2c74a760ceeaafe920dca141e0eb4b58792510 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 29 Sep 2021 19:52:35 -0700 Subject: [PATCH 013/174] Add type annotations to SortedArray --- src/observable/list/SortedArray.ts | 47 +++++++++++++++++------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/observable/list/SortedArray.ts b/src/observable/list/SortedArray.ts index 874d1b04..9feade07 100644 --- a/src/observable/list/SortedArray.ts +++ b/src/observable/list/SortedArray.ts @@ -18,18 +18,22 @@ import {BaseObservableList} from "./BaseObservableList"; import {sortedIndex} from "../../utils/sortedIndex.js"; import {findAndUpdateInArray} from "./common"; -export class SortedArray extends BaseObservableList { - constructor(comparator) { +declare function sortedIndex(array: T[], value: T, comparator: (left: T, right: T) => number): number; + +export class SortedArray extends BaseObservableList { + private _comparator: (left: T, right: T) => number; + private _items: T[] = []; + + constructor(comparator: (left: T, right: T) => number) { super(); this._comparator = comparator; - this._items = []; } - setManyUnsorted(items) { + setManyUnsorted(items: T[]): void { this.setManySorted(items); } - setManySorted(items) { + setManySorted(items: T[]): void { // TODO: we can make this way faster by only looking up the first and last key, // and merging whatever is inbetween with items // if items is not sorted, 💩🌀 will follow! @@ -42,11 +46,11 @@ export class SortedArray extends BaseObservableList { } } - findAndUpdate(predicate, updater) { + findAndUpdate(predicate: (value: T) => boolean, updater: (value: T) => any | false): boolean { return findAndUpdateInArray(predicate, this._items, this, updater); } - getAndUpdate(item, updater, updateParams = null) { + getAndUpdate(item: T, updater: (existing: T, item: T) => any, updateParams: any = null): void { const idx = this.indexOf(item); if (idx !== -1) { const existingItem = this._items[idx]; @@ -56,7 +60,7 @@ export class SortedArray extends BaseObservableList { } } - update(item, updateParams = null) { + update(item: T, updateParams: any = null): void { const idx = this.indexOf(item); if (idx !== -1) { this._items[idx] = item; @@ -64,7 +68,7 @@ export class SortedArray extends BaseObservableList { } } - indexOf(item) { + indexOf(item: T): number { const idx = sortedIndex(this._items, item, this._comparator); if (idx < this._items.length && this._comparator(this._items[idx], item) === 0) { return idx; @@ -73,7 +77,7 @@ export class SortedArray extends BaseObservableList { } } - _getNext(item) { + _getNext(item: T): T | undefined { let idx = sortedIndex(this._items, item, this._comparator); while(idx < this._items.length && this._comparator(this._items[idx], item) <= 0) { idx += 1; @@ -81,7 +85,7 @@ export class SortedArray extends BaseObservableList { return this.get(idx); } - set(item, updateParams = null) { + set(item: T, updateParams: any = null): void { const idx = sortedIndex(this._items, item, this._comparator); if (idx >= this._items.length || this._comparator(this._items[idx], item) !== 0) { this._items.splice(idx, 0, item); @@ -92,21 +96,21 @@ export class SortedArray extends BaseObservableList { } } - get(idx) { + get(idx: number): T | undefined { return this._items[idx]; } - remove(idx) { + remove(idx: number): void { const item = this._items[idx]; this._items.splice(idx, 1); this.emitRemove(idx, item); } - get array() { + get array(): T[] { return this._items; } - get length() { + get length(): number { return this._items.length; } @@ -116,8 +120,11 @@ export class SortedArray extends BaseObservableList { } // iterator that works even if the current value is removed while iterating -class Iterator { - constructor(sortedArray) { +class Iterator { + private _sortedArray: SortedArray | null + private _current: T | null | undefined + + constructor(sortedArray: SortedArray) { this._sortedArray = sortedArray; this._current = null; } @@ -145,7 +152,7 @@ class Iterator { export function tests() { return { "setManyUnsorted": assert => { - const sa = new SortedArray((a, b) => a.localeCompare(b)); + const sa = new SortedArray((a, b) => a.localeCompare(b)); sa.setManyUnsorted(["b", "a", "c"]); assert.equal(sa.length, 3); assert.equal(sa.get(0), "a"); @@ -153,7 +160,7 @@ export function tests() { assert.equal(sa.get(2), "c"); }, "_getNext": assert => { - const sa = new SortedArray((a, b) => a.localeCompare(b)); + const sa = new SortedArray((a, b) => a.localeCompare(b)); sa.setManyUnsorted(["b", "a", "f"]); assert.equal(sa._getNext("a"), "b"); assert.equal(sa._getNext("b"), "f"); @@ -162,7 +169,7 @@ export function tests() { assert.equal(sa._getNext("f"), undefined); }, "iterator with removals": assert => { - const queue = new SortedArray((a, b) => a.idx - b.idx); + const queue = new SortedArray<{idx: number}>((a, b) => a.idx - b.idx); queue.setManyUnsorted([{idx: 5}, {idx: 3}, {idx: 1}, {idx: 4}, {idx: 2}]); const it = queue[Symbol.iterator](); assert.equal(it.next().value.idx, 1); From d31371b4865e53a7ada510b015c0ea9936ea336e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 8 Nov 2021 14:37:32 +0530 Subject: [PATCH 014/174] Return on upload in sw --- src/platform/web/docroot/sw.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/web/docroot/sw.js b/src/platform/web/docroot/sw.js index 29b124d9..21fdf2d2 100644 --- a/src/platform/web/docroot/sw.js +++ b/src/platform/web/docroot/sw.js @@ -69,6 +69,9 @@ async function purgeOldCaches() { } self.addEventListener('fetch', (event) => { + if (event.request.url.indexOf("upload") !== -1) { + return; + } event.respondWith(handleRequest(event.request)); }); From d1491cc2033cae8d666eaf6e0840514d99c29e10 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 8 Nov 2021 15:11:41 +0530 Subject: [PATCH 015/174] More checks before returning --- src/platform/web/docroot/sw.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/web/docroot/sw.js b/src/platform/web/docroot/sw.js index 21fdf2d2..9b5da128 100644 --- a/src/platform/web/docroot/sw.js +++ b/src/platform/web/docroot/sw.js @@ -68,8 +68,10 @@ async function purgeOldCaches() { } } -self.addEventListener('fetch', (event) => { - if (event.request.url.indexOf("upload") !== -1) { +self.addEventListener('fetch', async (event) => { + if (event.request.method === "POST" && + (await event.request.blob()).type !== "application/json" && + event.request.body) { return; } event.respondWith(handleRequest(event.request)); From e9586711e00c893054a4adc483b847ca52b476a2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 8 Nov 2021 11:19:24 +0100 Subject: [PATCH 016/174] add scrollbar when > 5 lines in composer --- src/platform/web/ui/css/themes/element/theme.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 1932ecdd..32e048ec 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -548,6 +548,8 @@ a { box-sizing: border-box; overflow: hidden; max-height: 113px; /* 5 lines */ + overflow-y: auto; + overflow-y: overlay; } .MessageComposer_input > button.send { From c1a8ffd814083c7003cbadac088e2d1239c9ba2f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 8 Nov 2021 18:03:20 +0530 Subject: [PATCH 017/174] respond with only for GET requests --- src/platform/web/docroot/sw.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform/web/docroot/sw.js b/src/platform/web/docroot/sw.js index 9b5da128..7eab4758 100644 --- a/src/platform/web/docroot/sw.js +++ b/src/platform/web/docroot/sw.js @@ -69,12 +69,9 @@ async function purgeOldCaches() { } self.addEventListener('fetch', async (event) => { - if (event.request.method === "POST" && - (await event.request.blob()).type !== "application/json" && - event.request.body) { - return; + if (event.request.method === "GET") { + event.respondWith(handleRequest(event.request)); } - event.respondWith(handleRequest(event.request)); }); function isCacheableThumbnail(url) { From 57e2c4ea45205195b29858dd16e508d5621a55f1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 8 Nov 2021 22:20:56 +0530 Subject: [PATCH 018/174] No need for handler to be async --- src/platform/web/docroot/sw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/docroot/sw.js b/src/platform/web/docroot/sw.js index 7eab4758..8542492c 100644 --- a/src/platform/web/docroot/sw.js +++ b/src/platform/web/docroot/sw.js @@ -68,7 +68,7 @@ async function purgeOldCaches() { } } -self.addEventListener('fetch', async (event) => { +self.addEventListener('fetch', (event) => { if (event.request.method === "GET") { event.respondWith(handleRequest(event.request)); } From dad37dece339ec18af53030b20754d48e11a16c2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 11:46:05 +0530 Subject: [PATCH 019/174] .js --> .ts --- src/logging/{BaseLogger.js => BaseLogger.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/logging/{BaseLogger.js => BaseLogger.ts} (100%) diff --git a/src/logging/BaseLogger.js b/src/logging/BaseLogger.ts similarity index 100% rename from src/logging/BaseLogger.js rename to src/logging/BaseLogger.ts From 030c46264b2e91b5271da67859a9de4d350448a2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 13:00:37 +0530 Subject: [PATCH 020/174] type annotate fields --- src/logging/BaseLogger.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 584c763b..51a32485 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -17,8 +17,12 @@ limitations under the License. import {LogItem} from "./LogItem.js"; import {LogLevel, LogFilter} from "./LogFilter.js"; +import {Platform} from "../platform/web/Platform.js"; export class BaseLogger { + protected _openItems: Set; + protected _platform: Platform; + constructor({platform}) { this._openItems = new Set(); this._platform = platform; From 377cc4ca1fb89fb576520ad8928bd9d7079a0bff Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 13:52:41 +0530 Subject: [PATCH 021/174] Make BaseLogger abstract --- src/logging/BaseLogger.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 51a32485..d45c2fd3 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -19,7 +19,7 @@ import {LogItem} from "./LogItem.js"; import {LogLevel, LogFilter} from "./LogFilter.js"; import {Platform} from "../platform/web/Platform.js"; -export class BaseLogger { +export abstract class BaseLogger { protected _openItems: Set; protected _platform: Platform; @@ -131,13 +131,9 @@ export class BaseLogger { this._openItems.clear(); } - _persistItem() { - throw new Error("not implemented"); - } + abstract _persistItem(item: LogItem, filter?: LogFilter, forced?: boolean): void; - async export() { - throw new Error("not implemented"); - } + abstract export(): void; // expose log level without needing get level() { From 839d3fb6899cf32d898bb704799a78b2a5d92f5e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 13:53:07 +0530 Subject: [PATCH 022/174] Throw on export() in ConsoleLogger --- src/logging/ConsoleLogger.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/logging/ConsoleLogger.js b/src/logging/ConsoleLogger.js index 9610795f..aacbf132 100644 --- a/src/logging/ConsoleLogger.js +++ b/src/logging/ConsoleLogger.js @@ -19,6 +19,10 @@ export class ConsoleLogger extends BaseLogger { _persistItem(item) { printToConsole(item); } + + export() { + throw new Error("Cannot export from ConsoleLogger"); + } } const excludedKeysFromTable = ["l", "id"]; From 8fba3f4ca9d40ee5c3c1ea6084d74c7b7552b964 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 15:39:24 +0530 Subject: [PATCH 023/174] Add explaining comment --- src/platform/web/docroot/sw.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platform/web/docroot/sw.js b/src/platform/web/docroot/sw.js index 8542492c..1dbe21f9 100644 --- a/src/platform/web/docroot/sw.js +++ b/src/platform/web/docroot/sw.js @@ -69,6 +69,11 @@ async function purgeOldCaches() { } self.addEventListener('fetch', (event) => { + /* + service worker shouldn't handle xhr uploads because otherwise + the progress events won't fire. + This has to do with xhr not being supported in service workers. + */ if (event.request.method === "GET") { event.respondWith(handleRequest(event.request)); } From eef116e26b087d68efb71f544d769ddf25ef53a7 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 17:19:46 +0530 Subject: [PATCH 024/174] annotate labelOrValues --- src/logging/BaseLogger.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index d45c2fd3..ab2d600b 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -19,6 +19,9 @@ import {LogItem} from "./LogItem.js"; import {LogLevel, LogFilter} from "./LogFilter.js"; import {Platform} from "../platform/web/Platform.js"; +// todo: move this to LogItem? +type LabelOrValues = string | {l: string; [key: string]: any}; + export abstract class BaseLogger { protected _openItems: Set; protected _platform: Platform; @@ -28,14 +31,14 @@ export abstract class BaseLogger { this._platform = platform; } - log(labelOrValues, logLevel = LogLevel.Info) { + log(labelOrValues: LabelOrValues, logLevel: number = LogLevel.Info) { const item = new LogItem(labelOrValues, logLevel, null, this); item._end = item._start; this._persistItem(item, null, false); } /** if item is a log item, wrap the callback in a child of it, otherwise start a new root log item. */ - wrapOrRun(item, labelOrValues, callback, logLevel = null, filterCreator = null) { + wrapOrRun(item, labelOrValues: LabelOrValues, callback, logLevel = null, filterCreator = null) { if (item) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { @@ -48,7 +51,7 @@ export abstract class BaseLogger { Useful to pair with LogItem.refDetached. @return {LogItem} the log item added, useful to pass to LogItem.refDetached */ - runDetached(labelOrValues, callback, logLevel = null, filterCreator = null) { + runDetached(labelOrValues: LabelOrValues, callback, logLevel = null, filterCreator = null) { if (logLevel === null) { logLevel = LogLevel.Info; } @@ -60,7 +63,7 @@ export abstract class BaseLogger { /** run a callback wrapped in a log operation. Errors and duration are transparently logged, also for async operations. Whatever the callback returns is returned here. */ - run(labelOrValues, callback, logLevel = null, filterCreator = null) { + run(labelOrValues: LabelOrValues, callback, logLevel = null, filterCreator = null) { if (logLevel === null) { logLevel = LogLevel.Info; } From 4c5d0285091ae4e3eba5e50ca66fa05a8888301f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 17:34:16 +0530 Subject: [PATCH 025/174] any --> unknown --- src/logging/BaseLogger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index ab2d600b..bd9b6811 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -20,7 +20,7 @@ import {LogLevel, LogFilter} from "./LogFilter.js"; import {Platform} from "../platform/web/Platform.js"; // todo: move this to LogItem? -type LabelOrValues = string | {l: string; [key: string]: any}; +type LabelOrValues = string | {l: string; [key: string]: unknown}; export abstract class BaseLogger { protected _openItems: Set; From 7893a121c0d85cf8e747cbfa82005444a96316de Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 17:36:18 +0530 Subject: [PATCH 026/174] Initialize in field --- src/logging/BaseLogger.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index bd9b6811..311af22d 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -23,11 +23,10 @@ import {Platform} from "../platform/web/Platform.js"; type LabelOrValues = string | {l: string; [key: string]: unknown}; export abstract class BaseLogger { - protected _openItems: Set; + protected _openItems: Set = new Set(); protected _platform: Platform; constructor({platform}) { - this._openItems = new Set(); this._platform = platform; } From 8cbc81b8bb715f4d3f0bb441998459c5276af6cc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 20:57:47 +0530 Subject: [PATCH 027/174] Annotate method arguments --- src/logging/BaseLogger.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 311af22d..7117f9d9 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -21,6 +21,10 @@ import {Platform} from "../platform/web/Platform.js"; // todo: move this to LogItem? type LabelOrValues = string | {l: string; [key: string]: unknown}; +type LogCallback = (item: LogItem) => Promise | undefined; +// todo: this should be an enum +type LogLevel = number | null; +type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | null; export abstract class BaseLogger { protected _openItems: Set = new Set(); @@ -37,7 +41,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. */ - wrapOrRun(item, labelOrValues: LabelOrValues, callback, logLevel = null, filterCreator = null) { + wrapOrRun(item: LogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel = null, filterCreator: FilterCreator = null) { if (item) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { @@ -50,27 +54,27 @@ export abstract class BaseLogger { Useful to pair with LogItem.refDetached. @return {LogItem} the log item added, useful to pass to LogItem.refDetached */ - runDetached(labelOrValues: LabelOrValues, callback, logLevel = null, filterCreator = null) { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel = null, filterCreator: FilterCreator = null) { if (logLevel === null) { logLevel = LogLevel.Info; } const item = new LogItem(labelOrValues, logLevel, null, this); - this._run(item, callback, logLevel, filterCreator, false /* don't throw, nobody is awaiting */); + this._run(item, callback, logLevel!, filterCreator, false /* don't throw, nobody is awaiting */); return item; } /** run a callback wrapped in a log operation. Errors and duration are transparently logged, also for async operations. Whatever the callback returns is returned here. */ - run(labelOrValues: LabelOrValues, callback, logLevel = null, filterCreator = null) { + run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel = null, filterCreator: FilterCreator = null) { if (logLevel === null) { logLevel = LogLevel.Info; } const item = new LogItem(labelOrValues, logLevel, null, this); - return this._run(item, callback, logLevel, filterCreator, true); + return this._run(item, callback, logLevel!, filterCreator, true); } - _run(item, callback, logLevel, filterCreator, shouldThrow) { + _run(item: LogItem, callback: LogCallback, logLevel: number, filterCreator: FilterCreator, shouldThrow: boolean) { this._openItems.add(item); const finishItem = () => { From 55401a746c6d46e57fedfdb75932bf8e109247b7 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 22:28:26 +0530 Subject: [PATCH 028/174] Move type alias to LogItem and add more type annotations --- src/logging/BaseLogger.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 7117f9d9..27dd4983 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -15,16 +15,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {LogItem} from "./LogItem.js"; +import {LogItem, LabelOrValues, FilterCreator} from "./LogItem"; import {LogLevel, LogFilter} from "./LogFilter.js"; import {Platform} from "../platform/web/Platform.js"; -// todo: move this to LogItem? -type LabelOrValues = string | {l: string; [key: string]: unknown}; type LogCallback = (item: LogItem) => Promise | undefined; -// todo: this should be an enum -type LogLevel = number | null; -type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | null; export abstract class BaseLogger { protected _openItems: Set = new Set(); @@ -36,7 +31,7 @@ export abstract class BaseLogger { log(labelOrValues: LabelOrValues, logLevel: number = LogLevel.Info) { const item = new LogItem(labelOrValues, logLevel, null, this); - item._end = item._start; + item.end = item.start; this._persistItem(item, null, false); } @@ -146,7 +141,7 @@ export abstract class BaseLogger { return LogLevel; } - _now() { + _now(): number { return this._platform.clock.now(); } From 2a5d30d749e5a401f33b860f591d532e9c415d9e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 9 Nov 2021 22:32:02 +0530 Subject: [PATCH 029/174] Convert to enum --- src/logging/LogFilter.ts | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/logging/LogFilter.ts diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts new file mode 100644 index 00000000..83f71891 --- /dev/null +++ b/src/logging/LogFilter.ts @@ -0,0 +1,54 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + + +export enum LogLevel { + All = 1, + Debug, + Detail, + Info, + Warn, + Error, + Fatal, + Off +} + +export class LogFilter { + constructor(parentFilter) { + this._parentFilter = parentFilter; + this._min = null; + } + + filter(item, children) { + if (this._parentFilter) { + if (!this._parentFilter.filter(item, children)) { + return false; + } + } + // neither our children or us have a loglevel high enough, filter out. + if (this._min !== null && !Array.isArray(children) && item.logLevel < this._min) { + return false; + } else { + return true; + } + } + + /* methods to build the filter */ + minLevel(logLevel) { + this._min = logLevel; + return this; + } +} From ba4d5453a23ca7185142d628785b758aa1aefdfc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 12:05:29 +0530 Subject: [PATCH 030/174] Move type LogCallback to LogItem --- src/logging/BaseLogger.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 27dd4983..c47329c2 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -15,11 +15,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {LogItem, LabelOrValues, FilterCreator} from "./LogItem"; +import {LogItem, LabelOrValues, FilterCreator, LogCallback} from "./LogItem"; import {LogLevel, LogFilter} from "./LogFilter.js"; import {Platform} from "../platform/web/Platform.js"; -type LogCallback = (item: LogItem) => Promise | undefined; export abstract class BaseLogger { protected _openItems: Set = new Set(); From 97ec680af2af2676d2ba1faa238198840571c79e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 12:06:22 +0530 Subject: [PATCH 031/174] Remove .js files --- src/logging/LogFilter.js | 53 --------- src/logging/LogItem.js | 242 --------------------------------------- 2 files changed, 295 deletions(-) delete mode 100644 src/logging/LogFilter.js delete mode 100644 src/logging/LogItem.js diff --git a/src/logging/LogFilter.js b/src/logging/LogFilter.js deleted file mode 100644 index 81bbb33c..00000000 --- a/src/logging/LogFilter.js +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2021 The Matrix.org Foundation C.I.C. - -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. -*/ - -export const LogLevel = { - All: 1, - Debug: 2, - Detail: 3, - Info: 4, - Warn: 5, - Error: 6, - Fatal: 7, - Off: 8, -} - -export class LogFilter { - constructor(parentFilter) { - this._parentFilter = parentFilter; - this._min = null; - } - - filter(item, children) { - if (this._parentFilter) { - if (!this._parentFilter.filter(item, children)) { - return false; - } - } - // neither our children or us have a loglevel high enough, filter out. - if (this._min !== null && !Array.isArray(children) && item.logLevel < this._min) { - return false; - } else { - return true; - } - } - - /* methods to build the filter */ - minLevel(logLevel) { - this._min = logLevel; - return this; - } -} diff --git a/src/logging/LogItem.js b/src/logging/LogItem.js deleted file mode 100644 index 90747964..00000000 --- a/src/logging/LogItem.js +++ /dev/null @@ -1,242 +0,0 @@ -/* -Copyright 2020 Bruno Windels -Copyright 2021 The Matrix.org Foundation C.I.C. - -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 {LogLevel, LogFilter} from "./LogFilter.js"; - -export class LogItem { - constructor(labelOrValues, logLevel, filterCreator, logger) { - this._logger = logger; - this._start = logger._now(); - this._end = null; - // (l)abel - this._values = typeof labelOrValues === "string" ? {l: labelOrValues} : labelOrValues; - this.error = null; - this.logLevel = logLevel; - this._children = null; - this._filterCreator = filterCreator; - } - - /** start a new root log item and run it detached mode, see BaseLogger.runDetached */ - runDetached(labelOrValues, callback, logLevel, filterCreator) { - return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator); - } - - /** start a new detached root log item and log a reference to it from this item */ - wrapDetached(labelOrValues, callback, logLevel, filterCreator) { - this.refDetached(this.runDetached(labelOrValues, callback, logLevel, filterCreator)); - } - - /** logs a reference to a different log item, usually obtained from runDetached. - This is useful if the referenced operation can't be awaited. */ - refDetached(logItem, logLevel = null) { - logItem.ensureRefId(); - return this.log({ref: logItem._values.refId}, logLevel); - } - - ensureRefId() { - if (!this._values.refId) { - this.set("refId", this._logger._createRefId()); - } - } - - /** - * Creates a new child item and runs it in `callback`. - */ - wrap(labelOrValues, callback, logLevel = null, filterCreator = null) { - const item = this.child(labelOrValues, logLevel, filterCreator); - return item.run(callback); - } - - get duration() { - if (this._end) { - return this._end - this._start; - } else { - return null; - } - } - - durationWithoutType(type) { - return this.duration - this.durationOfType(type); - } - - durationOfType(type) { - if (this._values.t === type) { - return this.duration; - } else if (this._children) { - return this._children.reduce((sum, c) => { - return sum + c.durationOfType(type); - }, 0); - } else { - return 0; - } - } - - /** - * Creates a new child item that finishes immediately - * and can hence not be modified anymore. - * - * Hence, the child item is not returned. - */ - log(labelOrValues, logLevel = null) { - const item = this.child(labelOrValues, logLevel, null); - item._end = item._start; - } - - set(key, value) { - if(typeof key === "object") { - const values = key; - Object.assign(this._values, values); - } else { - this._values[key] = value; - } - } - - serialize(filter, parentStartTime = null, forced) { - if (this._filterCreator) { - try { - filter = this._filterCreator(new LogFilter(filter), this); - } catch (err) { - console.error("Error creating log filter", err); - } - } - let children; - if (this._children !== null) { - children = this._children.reduce((array, c) => { - const s = c.serialize(filter, this._start, false); - if (s) { - if (array === null) { - array = []; - } - array.push(s); - } - return array; - }, null); - } - if (filter && !filter.filter(this, children)) { - return null; - } - // in (v)alues, (l)abel and (t)ype are also reserved. - const item = { - // (s)tart - s: parentStartTime === null ? this._start : this._start - parentStartTime, - // (d)uration - d: this.duration, - // (v)alues - v: this._values, - // (l)evel - l: this.logLevel - }; - if (this.error) { - // (e)rror - item.e = { - stack: this.error.stack, - name: this.error.name, - message: this.error.message.split("\n")[0] - }; - } - if (forced) { - item.f = true; //(f)orced - } - if (children) { - // (c)hildren - item.c = children; - } - return item; - } - - /** - * You probably want to use `wrap` instead of this. - * - * Runs a callback passing this log item, - * recording the timing and any error. - * - * callback can return a Promise. - * - * Should only be called once. - * - * @param {Function} callback [description] - * @return {[type]} [description] - */ - run(callback) { - if (this._end !== null) { - console.trace("log item is finished, additional logs will likely not be recorded"); - } - let result; - try { - result = callback(this); - if (result instanceof Promise) { - return result.then(promiseResult => { - this.finish(); - return promiseResult; - }, err => { - throw this.catch(err); - }); - } else { - this.finish(); - return result; - } - } catch (err) { - throw this.catch(err); - } - } - - /** - * finished the item, recording the end time. After finishing, an item can't be modified anymore as it will be persisted. - * @internal shouldn't typically be called by hand. allows to force finish if a promise is still running when closing the app - */ - finish() { - if (this._end === null) { - if (this._children !== null) { - for(const c of this._children) { - c.finish(); - } - } - this._end = this._logger._now(); - } - } - - // expose log level without needing import everywhere - get level() { - return LogLevel; - } - - catch(err) { - this.error = err; - this.logLevel = LogLevel.Error; - this.finish(); - return err; - } - - child(labelOrValues, logLevel, filterCreator) { - if (this._end !== null) { - console.trace("log item is finished, additional logs will likely not be recorded"); - } - if (!logLevel) { - logLevel = this.logLevel || LogLevel.Info; - } - const item = new LogItem(labelOrValues, logLevel, filterCreator, this._logger); - if (this._children === null) { - this._children = []; - } - this._children.push(item); - return item; - } - - get logger() { - return this._logger; - } -} From db792ab5a91188f7e4d62f08689ef31cb3829f58 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 12:06:50 +0530 Subject: [PATCH 032/174] Add type annotations to LogItem --- src/logging/LogItem.ts | 270 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 src/logging/LogItem.ts diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts new file mode 100644 index 00000000..bd6958e1 --- /dev/null +++ b/src/logging/LogItem.ts @@ -0,0 +1,270 @@ +/* +Copyright 2020 Bruno Windels +Copyright 2021 The Matrix.org Foundation C.I.C. + +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 {BaseLogger} from "./BaseLogger"; +import {LogLevel, LogFilter} from "./LogFilter.js"; + +type LogItemValues = {l: string; [key: string]: unknown}; +export type LabelOrValues = string | LogItemValues; +export type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | null; +export type LogCallback = (item: LogItem) => Promise | undefined; + +export class LogItem { + public start: number; + public logLevel: LogLevel; + public error: Error | null; + public end: number | null; + private _values: LogItemValues; + private _logger: BaseLogger; + private _filterCreator: FilterCreator; + private _children: Array | null; + + constructor(labelOrValues: LabelOrValues, logLevel: LogLevel, filterCreator: FilterCreator, logger: BaseLogger) { + this._logger = logger; + this.start = logger._now(); + this.end = null; + // (l)abel + this._values = typeof labelOrValues === "string" ? {l: labelOrValues} : labelOrValues; + this.error = null; + this.logLevel = logLevel; + this._children = null; + this._filterCreator = filterCreator; + } + + /** start a new root log item and run it detached mode, see BaseLogger.runDetached */ + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator) { + return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator); + } + + /** start a new detached root log item and log a reference to it from this item */ + wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator) { + this.refDetached(this.runDetached(labelOrValues, callback, logLevel, filterCreator)); + } + + /** logs a reference to a different log item, usually obtained from runDetached. + This is useful if the referenced operation can't be awaited. */ + refDetached(logItem: LogItem, logLevel: LogLevel | null = null) { + logItem.ensureRefId(); + return this.log({ref: logItem._values.refId}, logLevel); + } + + ensureRefId() { + if (!this._values.refId) { + this.set("refId", this._logger._createRefId()); + } + } + + /** + * Creates a new child item and runs it in `callback`. + */ + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel | null = null, filterCreator: FilterCreator = null) { + const item = this.child(labelOrValues, logLevel, filterCreator); + return item.run(callback); + } + + get duration() { + if (this.end) { + return this.end - this.start; + } else { + return null; + } + } + + durationWithoutType(type) { + return this.duration - this.durationOfType(type); + } + + durationOfType(type) { + if (this._values.t === type) { + return this.duration; + } else if (this._children) { + return this._children.reduce((sum, c) => { + return sum + c.durationOfType(type); + }, 0); + } else { + return 0; + } + } + + /** + * Creates a new child item that finishes immediately + * and can hence not be modified anymore. + * + * Hence, the child item is not returned. + */ + log(labelOrValues: LabelOrValues, logLevel: LogLevel | null = null) { + const item = this.child(labelOrValues, logLevel, null); + item.end = item.start; + } + + set(key, value) { + if(typeof key === "object") { + const values = key; + Object.assign(this._values, values); + } else { + this._values[key] = value; + } + } + + serialize(filter: LogFilter, parentStartTime: number | null = null, forced: boolean) { + if (this._filterCreator) { + try { + filter = this._filterCreator(new LogFilter(filter), this); + } catch (err) { + console.error("Error creating log filter", err); + } + } + interface Item { + s: number; + d: number | null; + v: LogItemValues; + l: LogLevel; + e?: { + stack: string | undefined; + name: string; + message: string; + }; + f?: boolean; + c?: Array; + }; + let children: Array | null = null; + if (this._children !== null) { + children = this._children.reduce((array: Array, c) => { + const s = c.serialize(filter, this.start, false); + if (s) { + if (array === null) { + array = []; + } + array.push(s); + } + return array; + }, null); + } + if (filter && !filter.filter(this, children)) { + return null; + } + // in (v)alues, (l)abel and (t)ype are also reserved. + const item: Item = { + // (s)tart + s: parentStartTime === null ? this.start : this.start - parentStartTime, + // (d)uration + d: this.duration, + // (v)alues + v: this._values, + // (l)evel + l: this.logLevel + }; + if (this.error) { + // (e)rror + item.e = { + stack: this.error.stack, + name: this.error.name, + message: this.error.message.split("\n")[0] + }; + } + if (forced) { + item.f = true; //(f)orced + } + if (children) { + // (c)hildren + item.c = children; + } + return item; + } + + /** + * You probably want to use `wrap` instead of this. + * + * Runs a callback passing this log item, + * recording the timing and any error. + * + * callback can return a Promise. + * + * Should only be called once. + * + * @param {Function} callback [description] + * @return {[type]} [description] + */ + run(callback: LogCallback) { + if (this.end !== null) { + console.trace("log item is finished, additional logs will likely not be recorded"); + } + let result; + try { + result = callback(this); + if (result instanceof Promise) { + return result.then(promiseResult => { + this.finish(); + return promiseResult; + }, err => { + throw this.catch(err); + }); + } else { + this.finish(); + return result; + } + } catch (err) { + throw this.catch(err); + } + } + + /** + * finished the item, recording the end time. After finishing, an item can't be modified anymore as it will be persisted. + * @internal shouldn't typically be called by hand. allows to force finish if a promise is still running when closing the app + */ + finish() { + if (this.end === null) { + if (this._children !== null) { + for(const c of this._children) { + c.finish(); + } + } + this.end = this._logger._now(); + } + } + + // expose log level without needing import everywhere + get level() { + return LogLevel; + } + + catch(err: Error) { + this.error = err; + this.logLevel = LogLevel.Error; + this.finish(); + return err; + } + + child(labelOrValues: LabelOrValues, logLevel: LogLevel | null, filterCreator: FilterCreator) { + if (this.end !== null) { + console.trace("log item is finished, additional logs will likely not be recorded"); + } + if (!logLevel) { + logLevel = this.logLevel || LogLevel.Info; + } + const item = new LogItem(labelOrValues, logLevel, filterCreator, this._logger); + if (this._children === null) { + this._children = []; + } + this._children.push(item); + return item; + } + + get logger() { + return this._logger; + } +} From 772f7a2757c33ed0dd45a4c7b6a8c60f49c7fe94 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 12:17:43 +0530 Subject: [PATCH 033/174] Account for duration being null --- src/logging/LogItem.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index bd6958e1..0a896841 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -85,7 +85,10 @@ export class LogItem { } durationWithoutType(type) { - return this.duration - this.durationOfType(type); + if (this.duration) { + return this.duration - this.durationOfType(type); + } + return null; } durationOfType(type) { From ceb52eedafffe30b9593ca6e9e14a870cacbc55b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 12:36:56 +0530 Subject: [PATCH 034/174] Fix imports and add type annotations --- src/logging/BaseLogger.ts | 2 +- src/logging/ConsoleLogger.js | 2 +- src/logging/IDBLogger.js | 2 +- src/logging/LogItem.ts | 6 +++--- src/logging/NullLogger.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index c47329c2..60388f77 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -16,7 +16,7 @@ limitations under the License. */ import {LogItem, LabelOrValues, FilterCreator, LogCallback} from "./LogItem"; -import {LogLevel, LogFilter} from "./LogFilter.js"; +import {LogLevel, LogFilter} from "./LogFilter"; import {Platform} from "../platform/web/Platform.js"; diff --git a/src/logging/ConsoleLogger.js b/src/logging/ConsoleLogger.js index aacbf132..4d3be73b 100644 --- a/src/logging/ConsoleLogger.js +++ b/src/logging/ConsoleLogger.js @@ -13,7 +13,7 @@ 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 {BaseLogger} from "./BaseLogger.js"; +import {BaseLogger} from "./BaseLogger"; export class ConsoleLogger extends BaseLogger { _persistItem(item) { diff --git a/src/logging/IDBLogger.js b/src/logging/IDBLogger.js index 03c2bf88..2360b54c 100644 --- a/src/logging/IDBLogger.js +++ b/src/logging/IDBLogger.js @@ -22,7 +22,7 @@ import { iterateCursor, fetchResults, } from "../matrix/storage/idb/utils"; -import {BaseLogger} from "./BaseLogger.js"; +import {BaseLogger} from "./BaseLogger"; export class IDBLogger extends BaseLogger { constructor(options) { diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 0a896841..a77b7c3d 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -16,7 +16,7 @@ limitations under the License. */ import {BaseLogger} from "./BaseLogger"; -import {LogLevel, LogFilter} from "./LogFilter.js"; +import {LogLevel, LogFilter} from "./LogFilter"; type LogItemValues = {l: string; [key: string]: unknown}; export type LabelOrValues = string | LogItemValues; @@ -84,14 +84,14 @@ export class LogItem { } } - durationWithoutType(type) { + durationWithoutType(type: string) { if (this.duration) { return this.duration - this.durationOfType(type); } return null; } - durationOfType(type) { + durationOfType(type: string) { if (this._values.t === type) { return this.duration; } else if (this._children) { diff --git a/src/logging/NullLogger.js b/src/logging/NullLogger.js index 060212bd..12eec5c9 100644 --- a/src/logging/NullLogger.js +++ b/src/logging/NullLogger.js @@ -13,7 +13,7 @@ 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 {LogLevel} from "./LogFilter.js"; +import {LogLevel} from "./LogFilter"; function noop () {} From 142d3ef543321e248fc085bd80a7c64c6092b033 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 13:45:37 +0530 Subject: [PATCH 035/174] Split LogItemValues into union of types --- src/logging/LogItem.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index a77b7c3d..a7f05768 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -18,7 +18,25 @@ limitations under the License. import {BaseLogger} from "./BaseLogger"; import {LogLevel, LogFilter} from "./LogFilter"; -type LogItemValues = {l: string; [key: string]: unknown}; +type LogItemWithLabel = { + l: string; + [key: string]: unknown; +}; + +type LogItemNetwork = { + t: "network"; + method: string; + url: string; + [key: string]: unknown; +} + +type LogItemRef = { + ref: number; + [key: string]: unknown; +} + +type LogItemValues = LogItemWithLabel | LogItemNetwork | LogItemRef; + export type LabelOrValues = string | LogItemValues; export type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | null; export type LogCallback = (item: LogItem) => Promise | undefined; @@ -59,7 +77,7 @@ export class LogItem { This is useful if the referenced operation can't be awaited. */ refDetached(logItem: LogItem, logLevel: LogLevel | null = null) { logItem.ensureRefId(); - return this.log({ref: logItem._values.refId}, logLevel); + return this.log({ref: logItem._values.refId as number}, logLevel); } ensureRefId() { @@ -114,7 +132,7 @@ export class LogItem { item.end = item.start; } - set(key, value) { + set(key: string | object, value: unknown) { if(typeof key === "object") { const values = key; Object.assign(this._values, values); From 0b4eca47249e9a7dfa9272c40fcfd6c1faf2a0fe Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 14:29:23 +0530 Subject: [PATCH 036/174] Create alias for LogLevel | null --- src/logging/BaseLogger.ts | 12 ++++++------ src/logging/LogFilter.ts | 2 ++ src/logging/LogItem.ts | 14 +++++++------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 60388f77..bd17afde 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -16,7 +16,7 @@ limitations under the License. */ import {LogItem, LabelOrValues, FilterCreator, LogCallback} from "./LogItem"; -import {LogLevel, LogFilter} from "./LogFilter"; +import {LogLevel, LogFilter, LogLevelOrNull} from "./LogFilter"; import {Platform} from "../platform/web/Platform.js"; @@ -28,14 +28,14 @@ export abstract class BaseLogger { this._platform = platform; } - log(labelOrValues: LabelOrValues, logLevel: number = LogLevel.Info) { + log(labelOrValues: LabelOrValues, logLevel: LogLevel = LogLevel.Info) { const item = new LogItem(labelOrValues, logLevel, null, this); item.end = item.start; this._persistItem(item, null, false); } /** 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: LogLevel = null, filterCreator: FilterCreator = null) { + wrapOrRun(item: LogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null) { if (item) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { @@ -48,7 +48,7 @@ export abstract class BaseLogger { Useful to pair with LogItem.refDetached. @return {LogItem} the log item added, useful to pass to LogItem.refDetached */ - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel = null, filterCreator: FilterCreator = null) { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null) { if (logLevel === null) { logLevel = LogLevel.Info; } @@ -60,7 +60,7 @@ export abstract class BaseLogger { /** run a callback wrapped in a log operation. Errors and duration are transparently logged, also for async operations. Whatever the callback returns is returned here. */ - run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel = null, filterCreator: FilterCreator = null) { + run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null) { if (logLevel === null) { logLevel = LogLevel.Info; } @@ -131,7 +131,7 @@ export abstract class BaseLogger { this._openItems.clear(); } - abstract _persistItem(item: LogItem, filter?: LogFilter, forced?: boolean): void; + abstract _persistItem(item: LogItem, filter?: LogFilter | null, forced?: boolean): void; abstract export(): void; diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index 83f71891..3099d826 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -26,6 +26,8 @@ export enum LogLevel { Off } +export type LogLevelOrNull = LogLevel | null; + export class LogFilter { constructor(parentFilter) { this._parentFilter = parentFilter; diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index a7f05768..094dc7a2 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -16,7 +16,7 @@ limitations under the License. */ import {BaseLogger} from "./BaseLogger"; -import {LogLevel, LogFilter} from "./LogFilter"; +import {LogLevel, LogLevelOrNull, LogFilter} from "./LogFilter"; type LogItemWithLabel = { l: string; @@ -64,18 +64,18 @@ export class LogItem { } /** start a new root log item and run it detached mode, see BaseLogger.runDetached */ - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator) { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator) { return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator); } /** start a new detached root log item and log a reference to it from this item */ - wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator) { + wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator) { this.refDetached(this.runDetached(labelOrValues, callback, logLevel, filterCreator)); } /** logs a reference to a different log item, usually obtained from runDetached. This is useful if the referenced operation can't be awaited. */ - refDetached(logItem: LogItem, logLevel: LogLevel | null = null) { + refDetached(logItem: LogItem, logLevel: LogLevelOrNull = null) { logItem.ensureRefId(); return this.log({ref: logItem._values.refId as number}, logLevel); } @@ -89,7 +89,7 @@ export class LogItem { /** * Creates a new child item and runs it in `callback`. */ - wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevel | null = null, filterCreator: FilterCreator = null) { + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null) { const item = this.child(labelOrValues, logLevel, filterCreator); return item.run(callback); } @@ -127,7 +127,7 @@ export class LogItem { * * Hence, the child item is not returned. */ - log(labelOrValues: LabelOrValues, logLevel: LogLevel | null = null) { + log(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull = null) { const item = this.child(labelOrValues, logLevel, null); item.end = item.start; } @@ -270,7 +270,7 @@ export class LogItem { return err; } - child(labelOrValues: LabelOrValues, logLevel: LogLevel | null, filterCreator: FilterCreator) { + child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator) { if (this.end !== null) { console.trace("log item is finished, additional logs will likely not be recorded"); } From e3c85c585e2117c9f45cb5f4c91d56342d8fd0e1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 14:42:43 +0530 Subject: [PATCH 037/174] Log callbacks can return more than Promises --- src/logging/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 094dc7a2..4fdf8ada 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -39,7 +39,7 @@ type LogItemValues = LogItemWithLabel | LogItemNetwork | LogItemRef; export type LabelOrValues = string | LogItemValues; export type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | null; -export type LogCallback = (item: LogItem) => Promise | undefined; +export type LogCallback = (item: LogItem) => unknown; export class LogItem { public start: number; From ab126729e0ee3efc34e605ce267275e4519d4791 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 14:49:59 +0530 Subject: [PATCH 038/174] Use LogLevel as type instead of number --- src/logging/BaseLogger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index bd17afde..fb91e77b 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -68,7 +68,7 @@ export abstract class BaseLogger { return this._run(item, callback, logLevel!, filterCreator, true); } - _run(item: LogItem, callback: LogCallback, logLevel: number, filterCreator: FilterCreator, shouldThrow: boolean) { + _run(item: LogItem, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator, shouldThrow: boolean) { this._openItems.add(item); const finishItem = () => { From ef2aad89565a8940c3fe32cbae44338a2baceb78 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 15:04:07 +0530 Subject: [PATCH 039/174] Annotate LogFilter --- src/logging/LogFilter.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index 3099d826..f13a6179 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {LogItem} from "./LogItem"; export enum LogLevel { All = 1, @@ -29,12 +30,14 @@ export enum LogLevel { export type LogLevelOrNull = LogLevel | null; export class LogFilter { - constructor(parentFilter) { + private _min: LogLevelOrNull = null; + private _parentFilter?: LogFilter; + + constructor(parentFilter?: LogFilter) { this._parentFilter = parentFilter; - this._min = null; } - filter(item, children) { + filter(item: LogItem, children: Array | null) { if (this._parentFilter) { if (!this._parentFilter.filter(item, children)) { return false; @@ -49,7 +52,7 @@ export class LogFilter { } /* methods to build the filter */ - minLevel(logLevel) { + minLevel(logLevel: LogLevel) { this._min = logLevel; return this; } From cfa7708b57caafb131b8e4839243ad232fe54c7f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 18:51:46 +0530 Subject: [PATCH 040/174] Use type imports --- src/logging/BaseLogger.ts | 9 ++++++--- src/logging/LogFilter.ts | 2 +- src/logging/LogItem.ts | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index fb91e77b..99a8f9b7 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -15,9 +15,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {LogItem, LabelOrValues, FilterCreator, LogCallback} from "./LogItem"; -import {LogLevel, LogFilter, LogLevelOrNull} from "./LogFilter"; -import {Platform} from "../platform/web/Platform.js"; +import {LogItem} from "./LogItem"; +import {LogLevel, LogFilter} from "./LogFilter"; +import type {FilterCreator, LabelOrValues, LogCallback} from "./LogItem"; +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? +import type {Platform} from "../platform/web/Platform.js"; export abstract class BaseLogger { diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index f13a6179..476bca0e 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {LogItem} from "./LogItem"; +import type {LogItem} from "./LogItem"; export enum LogLevel { All = 1, diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 4fdf8ada..2e95813a 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -15,8 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {BaseLogger} from "./BaseLogger"; -import {LogLevel, LogLevelOrNull, LogFilter} from "./LogFilter"; +import {LogLevel, LogFilter} from "./LogFilter"; +import type {LogLevelOrNull} from "./LogFilter"; +import type {BaseLogger} from "./BaseLogger"; type LogItemWithLabel = { l: string; From 7a68c971aa36c81317a6ccacbb7cbf7a4dc6ade1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 19:07:24 +0530 Subject: [PATCH 041/174] Make field readonly --- src/logging/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 2e95813a..f58a0185 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -43,7 +43,7 @@ export type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | export type LogCallback = (item: LogItem) => unknown; export class LogItem { - public start: number; + public readonly start: number; public logLevel: LogLevel; public error: Error | null; public end: number | null; From cd7dccd804852ed38285e7786dc8bc9a5ab6a332 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 10 Nov 2021 19:13:35 +0530 Subject: [PATCH 042/174] Move interface to top --- src/logging/LogItem.ts | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index f58a0185..b0412b02 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -38,6 +38,20 @@ type LogItemRef = { type LogItemValues = LogItemWithLabel | LogItemNetwork | LogItemRef; +interface ISerializedItem { + s: number; + d: number | null; + v: LogItemValues; + l: LogLevel; + e?: { + stack?: string; + name: string; + message: string; + }; + f?: boolean; + c?: Array; +}; + export type LabelOrValues = string | LogItemValues; export type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | null; export type LogCallback = (item: LogItem) => unknown; @@ -150,22 +164,9 @@ export class LogItem { console.error("Error creating log filter", err); } } - interface Item { - s: number; - d: number | null; - v: LogItemValues; - l: LogLevel; - e?: { - stack: string | undefined; - name: string; - message: string; - }; - f?: boolean; - c?: Array; - }; - let children: Array | null = null; + let children: Array | null = null; if (this._children !== null) { - children = this._children.reduce((array: Array, c) => { + children = this._children.reduce((array: Array, c) => { const s = c.serialize(filter, this.start, false); if (s) { if (array === null) { @@ -180,7 +181,7 @@ export class LogItem { return null; } // in (v)alues, (l)abel and (t)ype are also reserved. - const item: Item = { + const item: ISerializedItem = { // (s)tart s: parentStartTime === null ? this.start : this.start - parentStartTime, // (d)uration From 0f7a78ee25b84f3c16800633bdf6d1f9b0c5a674 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 11 Nov 2021 13:05:12 +0530 Subject: [PATCH 043/174] Make return type explicit --- src/logging/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index b0412b02..c33e529f 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -156,7 +156,7 @@ export class LogItem { } } - serialize(filter: LogFilter, parentStartTime: number | null = null, forced: boolean) { + serialize(filter: LogFilter, parentStartTime: number | null = null, forced: boolean): ISerializedItem | null { if (this._filterCreator) { try { filter = this._filterCreator(new LogFilter(filter), this); From 425a3c85a990ca775112d4b2877d78d388d5900d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 11 Nov 2021 13:24:52 +0530 Subject: [PATCH 044/174] Make error prop private and expose via getter --- src/logging/LogItem.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index c33e529f..a464c285 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -59,7 +59,7 @@ export type LogCallback = (item: LogItem) => unknown; export class LogItem { public readonly start: number; public logLevel: LogLevel; - public error: Error | null; + private _error: Error | null; public end: number | null; private _values: LogItemValues; private _logger: BaseLogger; @@ -72,7 +72,7 @@ export class LogItem { this.end = null; // (l)abel this._values = typeof labelOrValues === "string" ? {l: labelOrValues} : labelOrValues; - this.error = null; + this._error = null; this.logLevel = logLevel; this._children = null; this._filterCreator = filterCreator; @@ -191,12 +191,12 @@ export class LogItem { // (l)evel l: this.logLevel }; - if (this.error) { + if (this._error) { // (e)rror item.e = { - stack: this.error.stack, - name: this.error.name, - message: this.error.message.split("\n")[0] + stack: this._error.stack, + name: this._error.name, + message: this._error.message.split("\n")[0] }; } if (forced) { @@ -266,7 +266,7 @@ export class LogItem { } catch(err: Error) { - this.error = err; + this._error = err; this.logLevel = LogLevel.Error; this.finish(); return err; @@ -290,4 +290,8 @@ export class LogItem { get logger() { return this._logger; } + + get error() { + return this._error; + } } From 09851600f7863ace096bb6adb476bb573aa7e745 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 11 Nov 2021 15:35:51 +0530 Subject: [PATCH 045/174] Remove unwanted types --- src/logging/LogItem.ts | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index a464c285..4289aafc 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -19,25 +19,6 @@ import {LogLevel, LogFilter} from "./LogFilter"; import type {LogLevelOrNull} from "./LogFilter"; import type {BaseLogger} from "./BaseLogger"; -type LogItemWithLabel = { - l: string; - [key: string]: unknown; -}; - -type LogItemNetwork = { - t: "network"; - method: string; - url: string; - [key: string]: unknown; -} - -type LogItemRef = { - ref: number; - [key: string]: unknown; -} - -type LogItemValues = LogItemWithLabel | LogItemNetwork | LogItemRef; - interface ISerializedItem { s: number; d: number | null; @@ -52,6 +33,16 @@ interface ISerializedItem { c?: Array; }; +type LogItemValues = { + l?: string; + t?: string; + id?: unknown; + status?: string | number; + refId?: number; + ref?: number; + [key: string]: any +} + export type LabelOrValues = string | LogItemValues; export type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | null; export type LogCallback = (item: LogItem) => unknown; From eb7c5c44370d3fbc359d5832ea73e61dd36b991d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 11 Nov 2021 16:08:25 +0530 Subject: [PATCH 046/174] Use undefined only instead of both undefined and null --- src/logging/BaseLogger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 99a8f9b7..259c47eb 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -34,7 +34,7 @@ export abstract class BaseLogger { log(labelOrValues: LabelOrValues, logLevel: LogLevel = LogLevel.Info) { const item = new LogItem(labelOrValues, logLevel, null, this); item.end = item.start; - this._persistItem(item, null, false); + this._persistItem(item, undefined, false); } /** if item is a log item, wrap the callback in a child of it, otherwise start a new root log item. */ @@ -134,7 +134,7 @@ export abstract class BaseLogger { this._openItems.clear(); } - abstract _persistItem(item: LogItem, filter?: LogFilter | null, forced?: boolean): void; + abstract _persistItem(item: LogItem, filter?: LogFilter, forced?: boolean): void; abstract export(): void; From 9fed2ca41b28be42cc89f6a1260f9b94c4487182 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 11 Nov 2021 16:25:14 +0530 Subject: [PATCH 047/174] Use undefined instead of null --- src/logging/LogFilter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index 476bca0e..4da7d457 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -30,7 +30,7 @@ export enum LogLevel { export type LogLevelOrNull = LogLevel | null; export class LogFilter { - private _min: LogLevelOrNull = null; + private _min?: LogLevel; private _parentFilter?: LogFilter; constructor(parentFilter?: LogFilter) { @@ -44,7 +44,7 @@ export class LogFilter { } } // neither our children or us have a loglevel high enough, filter out. - if (this._min !== null && !Array.isArray(children) && item.logLevel < this._min) { + if (this._min && !Array.isArray(children) && item.logLevel < this._min) { return false; } else { return true; From 2ddd2d16ed72b449236f1d25fbf834e8aa5ac979 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 11 Nov 2021 16:50:46 +0530 Subject: [PATCH 048/174] IDBLogger.js --> IDBLogger.ts --- src/logging/{IDBLogger.js => IDBLogger.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/logging/{IDBLogger.js => IDBLogger.ts} (100%) diff --git a/src/logging/IDBLogger.js b/src/logging/IDBLogger.ts similarity index 100% rename from src/logging/IDBLogger.js rename to src/logging/IDBLogger.ts From f3d0f88f957a88bd2ecc48907e4ed558a2328c38 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 12 Nov 2021 15:06:11 +0530 Subject: [PATCH 049/174] Make error public --- src/logging/LogItem.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 4289aafc..64ed6603 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -50,7 +50,7 @@ export type LogCallback = (item: LogItem) => unknown; export class LogItem { public readonly start: number; public logLevel: LogLevel; - private _error: Error | null; + public error: Error | null; public end: number | null; private _values: LogItemValues; private _logger: BaseLogger; @@ -63,7 +63,7 @@ export class LogItem { this.end = null; // (l)abel this._values = typeof labelOrValues === "string" ? {l: labelOrValues} : labelOrValues; - this._error = null; + this.error = null; this.logLevel = logLevel; this._children = null; this._filterCreator = filterCreator; @@ -182,12 +182,12 @@ export class LogItem { // (l)evel l: this.logLevel }; - if (this._error) { + if (this.error) { // (e)rror item.e = { - stack: this._error.stack, - name: this._error.name, - message: this._error.message.split("\n")[0] + stack: this.error.stack, + name: this.error.name, + message: this.error.message.split("\n")[0] }; } if (forced) { @@ -257,7 +257,7 @@ export class LogItem { } catch(err: Error) { - this._error = err; + this.error = err; this.logLevel = LogLevel.Error; this.finish(); return err; @@ -281,8 +281,4 @@ export class LogItem { get logger() { return this._logger; } - - get error() { - return this._error; - } } From 8c7a765e1114762a65be741407e16613e5012877 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 12 Nov 2021 15:06:21 +0530 Subject: [PATCH 050/174] Convert IDBLogger to ts --- src/logging/IDBLogger.ts | 25 +++++++++++++++++++++---- src/platform/web/Platform.js | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 2360b54c..93ba9549 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -23,8 +23,21 @@ import { fetchResults, } from "../matrix/storage/idb/utils"; import {BaseLogger} from "./BaseLogger"; +import type {Interval} from "../platform/web/dom/Clock"; +import type {Platform} from "../platform/web/Platform.js"; +import type {BlobHandle} from "../platform/web/dom/BlobHandle.js"; + +type QueuedItem = { + json: string; + id?: number; +} export class IDBLogger extends BaseLogger { + private readonly _name: string; + private readonly _limit: number; + private readonly _flushInterval: Interval; + private _queuedItems: QueuedItem[]; + constructor(options) { super(options); const {name, flushInterval = 60 * 1000, limit = 3000} = options; @@ -121,7 +134,7 @@ export class IDBLogger extends BaseLogger { try { const txn = db.transaction(["logs"], "readonly"); const logs = txn.objectStore("logs"); - const storedItems = await fetchResults(logs.openCursor(), () => false); + const storedItems: QueuedItem[] = await fetchResults(logs.openCursor(), () => false); const allItems = storedItems.concat(this._queuedItems); return new IDBLogExport(allItems, this, this._platform); } finally { @@ -154,7 +167,11 @@ export class IDBLogger extends BaseLogger { } class IDBLogExport { - constructor(items, logger, platform) { + private readonly _items: QueuedItem[]; + private readonly _logger: IDBLogger; + private readonly _platform: Platform; + + constructor(items: QueuedItem[], logger: IDBLogger, platform: Platform) { this._items = items; this._logger = logger; this._platform = platform; @@ -171,7 +188,7 @@ class IDBLogExport { return this._logger._removeItems(this._items); } - asBlob() { + asBlob(): BlobHandle { const log = { formatVersion: 1, appVersion: this._platform.updateService?.version, @@ -179,7 +196,7 @@ class IDBLogExport { }; const json = JSON.stringify(log); const buffer = this._platform.encoding.utf8.encode(json); - const blob = this._platform.createBlob(buffer, "application/json"); + const blob: BlobHandle = this._platform.createBlob(buffer, "application/json"); return blob; } } diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 1530ed12..63cbac2e 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -21,7 +21,7 @@ import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionI import {SettingsStorage} from "./dom/SettingsStorage.js"; import {Encoding} from "./utils/Encoding.js"; import {OlmWorker} from "../../matrix/e2ee/OlmWorker.js"; -import {IDBLogger} from "../../logging/IDBLogger.js"; +import {IDBLogger} from "../../logging/IDBLogger"; import {ConsoleLogger} from "../../logging/ConsoleLogger.js"; import {RootView} from "./ui/RootView.js"; import {Clock} from "./dom/Clock.js"; From 29a826051475f61ea945fb5500eda4a12b280c26 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 12 Nov 2021 23:12:15 +0530 Subject: [PATCH 051/174] Add explicit types for return in methods --- src/logging/LogItem.ts | 35 +++++++++++++----------- src/matrix/storage/idb/StorageFactory.ts | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 64ed6603..2909a84e 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -21,7 +21,7 @@ import type {BaseLogger} from "./BaseLogger"; interface ISerializedItem { s: number; - d: number | null; + d?: number; v: LogItemValues; l: LogLevel; e?: { @@ -70,7 +70,7 @@ export class LogItem { } /** start a new root log item and run it detached mode, see BaseLogger.runDetached */ - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator) { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator): LogItem { return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator); } @@ -83,7 +83,7 @@ export class LogItem { This is useful if the referenced operation can't be awaited. */ refDetached(logItem: LogItem, logLevel: LogLevelOrNull = null) { logItem.ensureRefId(); - return this.log({ref: logItem._values.refId as number}, logLevel); + this.log({ref: logItem._values.refId as number}, logLevel); } ensureRefId() { @@ -100,27 +100,30 @@ export class LogItem { return item.run(callback); } - get duration() { + get duration(): number | undefined{ if (this.end) { return this.end - this.start; } else { - return null; + return undefined; } } - durationWithoutType(type: string) { - if (this.duration) { - return this.duration - this.durationOfType(type); + durationWithoutType(type: string): number | undefined{ + const durationOfType = this.durationOfType(type); + if (this.duration && durationOfType) { + return this.duration - durationOfType; } - return null; } - durationOfType(type: string) { + durationOfType(type: string): number | undefined { if (this._values.t === type) { return this.duration; } else if (this._children) { return this._children.reduce((sum, c) => { - return sum + c.durationOfType(type); + const duration = c.durationOfType(type); + if (duration) { + return sum + duration; + } }, 0); } else { return 0; @@ -213,11 +216,11 @@ export class LogItem { * @param {Function} callback [description] * @return {[type]} [description] */ - run(callback: LogCallback) { + run(callback: LogCallback): unknown { if (this.end !== null) { console.trace("log item is finished, additional logs will likely not be recorded"); } - let result; + let result: unknown; try { result = callback(this); if (result instanceof Promise) { @@ -252,18 +255,18 @@ export class LogItem { } // expose log level without needing import everywhere - get level() { + get level(): typeof LogLevel { return LogLevel; } - catch(err: Error) { + catch(err: Error): Error { this.error = err; this.logLevel = LogLevel.Error; this.finish(); return err; } - child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator) { + child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator): LogItem { if (this.end !== null) { console.trace("log item is finished, additional logs will likely not be recorded"); } diff --git a/src/matrix/storage/idb/StorageFactory.ts b/src/matrix/storage/idb/StorageFactory.ts index 71201842..4ae55612 100644 --- a/src/matrix/storage/idb/StorageFactory.ts +++ b/src/matrix/storage/idb/StorageFactory.ts @@ -101,5 +101,5 @@ async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: nu const migrationFunc = schema[i]; await log.wrap(`v${i + 1}`, log => migrationFunc(db, txn, localStorage, log)); } - }); + }) as Promise; } From 8e42e3f21f23a85c65893a47a9868ccaedc67277 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 12 Nov 2021 23:17:21 +0530 Subject: [PATCH 052/174] Add types to returns in LogFilter.ts --- src/logging/LogFilter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index 4da7d457..480e92db 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -37,7 +37,7 @@ export class LogFilter { this._parentFilter = parentFilter; } - filter(item: LogItem, children: Array | null) { + filter(item: LogItem, children: Array | null): boolean { if (this._parentFilter) { if (!this._parentFilter.filter(item, children)) { return false; @@ -52,7 +52,7 @@ export class LogFilter { } /* methods to build the filter */ - minLevel(logLevel: LogLevel) { + minLevel(logLevel: LogLevel): LogFilter { this._min = logLevel; return this; } From 67e8fc0c43ea2ed1508a0a4b3d751a4f9248c143 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 12 Nov 2021 23:27:25 +0530 Subject: [PATCH 053/174] Add return types to methods in BaseLogger --- src/logging/BaseLogger.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 259c47eb..9865355a 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -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. */ - wrapOrRun(item: LogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null) { + wrapOrRun(item: LogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): unknown { if (item) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { @@ -51,7 +51,8 @@ export abstract class BaseLogger { Useful to pair with 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) { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): LogItem { + // todo: Remove jsdoc type? if (logLevel === null) { logLevel = LogLevel.Info; } @@ -63,7 +64,7 @@ export abstract class BaseLogger { /** run a callback wrapped in a log operation. Errors and duration are transparently logged, also for async operations. Whatever the callback returns is returned here. */ - run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null) { + run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): unknown { if (logLevel === null) { logLevel = LogLevel.Info; } @@ -71,7 +72,7 @@ export abstract class BaseLogger { return this._run(item, callback, logLevel!, filterCreator, true); } - _run(item: LogItem, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator, shouldThrow: boolean) { + _run(item: LogItem, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator, shouldThrow: boolean): unknown { this._openItems.add(item); const finishItem = () => { @@ -139,7 +140,7 @@ export abstract class BaseLogger { abstract export(): void; // expose log level without needing - get level() { + get level(): typeof LogLevel { return LogLevel; } @@ -147,7 +148,7 @@ export abstract class BaseLogger { return this._platform.clock.now(); } - _createRefId() { + _createRefId(): number { return Math.round(this._platform.random() * Number.MAX_SAFE_INTEGER); } } From 5efa27c2a3c2297885b99bdeb14ac60c8bd5d5be Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 14 Nov 2021 15:48:59 +0530 Subject: [PATCH 054/174] Add more type annotations --- src/logging/IDBLogger.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 93ba9549..2ef5b551 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -26,6 +26,8 @@ import {BaseLogger} from "./BaseLogger"; import type {Interval} from "../platform/web/dom/Clock"; import type {Platform} from "../platform/web/Platform.js"; import type {BlobHandle} from "../platform/web/dom/BlobHandle.js"; +import type {LogItem} from "./LogItem"; +import type {LogFilter} from "./LogFilter"; type QueuedItem = { json: string; @@ -54,7 +56,7 @@ export class IDBLogger extends BaseLogger { this._flushInterval.dispose(); } - handleEvent(evt) { + handleEvent(evt: Event) { if (evt.type === "pagehide") { this._finishAllAndFlush(); } @@ -96,7 +98,7 @@ export class IDBLogger extends BaseLogger { this._persistQueuedItems(this._queuedItems); } - _loadQueuedItems() { + _loadQueuedItems(): QueuedItem[] { const key = `${this._name}_queuedItems`; try { const json = window.localStorage.getItem(key); @@ -110,18 +112,18 @@ export class IDBLogger extends BaseLogger { return []; } - _openDB() { + _openDB(): Promise { return openDatabase(this._name, db => db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}), 1); } - _persistItem(logItem, filter, forced) { - const serializedItem = logItem.serialize(filter, forced); + _persistItem(logItem: LogItem, filter: LogFilter, forced: boolean) { + const serializedItem = logItem.serialize(filter, undefined, forced); this._queuedItems.push({ json: JSON.stringify(serializedItem) }); } - _persistQueuedItems(items) { + _persistQueuedItems(items: QueuedItem[]) { try { window.localStorage.setItem(`${this._name}_queuedItems`, JSON.stringify(items)); } catch (e) { @@ -129,7 +131,7 @@ export class IDBLogger extends BaseLogger { } } - async export() { + async export(): Promise { const db = await this._openDB(); try { const txn = db.transaction(["logs"], "readonly"); @@ -144,7 +146,7 @@ export class IDBLogger extends BaseLogger { } } - async _removeItems(items) { + async _removeItems(items: QueuedItem[]) { const db = await this._openDB(); try { const txn = db.transaction(["logs"], "readwrite"); @@ -177,7 +179,7 @@ class IDBLogExport { this._platform = platform; } - get count() { + get count(): number { return this._items.length; } @@ -195,7 +197,7 @@ class IDBLogExport { items: this._items.map(i => JSON.parse(i.json)) }; const json = JSON.stringify(log); - const buffer = this._platform.encoding.utf8.encode(json); + const buffer: Uint8Array = this._platform.encoding.utf8.encode(json); const blob: BlobHandle = this._platform.createBlob(buffer, "application/json"); return blob; } From 2d8b719ab0a5be648e5d29e6e228b4a538f9f64b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 14 Nov 2021 15:55:42 +0530 Subject: [PATCH 055/174] Add void return types as well --- src/logging/IDBLogger.ts | 16 ++++++++-------- src/logging/LogItem.ts | 17 +++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 2ef5b551..4f6301ce 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -51,18 +51,18 @@ export class IDBLogger extends BaseLogger { this._flushInterval = this._platform.clock.createInterval(() => this._tryFlush(), flushInterval); } - dispose() { + dispose(): void { window.removeEventListener("pagehide", this, false); this._flushInterval.dispose(); } - handleEvent(evt: Event) { + handleEvent(evt: Event): void { if (evt.type === "pagehide") { this._finishAllAndFlush(); } } - async _tryFlush() { + async _tryFlush(): Promise { const db = await this._openDB(); try { const txn = db.transaction(["logs"], "readwrite"); @@ -92,7 +92,7 @@ export class IDBLogger extends BaseLogger { } } - _finishAllAndFlush() { + _finishAllAndFlush(): void { this._finishOpenItems(); this.log({l: "pagehide, closing logs", t: "navigation"}); this._persistQueuedItems(this._queuedItems); @@ -116,14 +116,14 @@ export class IDBLogger extends BaseLogger { return openDatabase(this._name, db => db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}), 1); } - _persistItem(logItem: LogItem, filter: LogFilter, forced: boolean) { + _persistItem(logItem: LogItem, filter: LogFilter, forced: boolean): void { const serializedItem = logItem.serialize(filter, undefined, forced); this._queuedItems.push({ json: JSON.stringify(serializedItem) }); } - _persistQueuedItems(items: QueuedItem[]) { + _persistQueuedItems(items: QueuedItem[]): void { try { window.localStorage.setItem(`${this._name}_queuedItems`, JSON.stringify(items)); } catch (e) { @@ -146,7 +146,7 @@ export class IDBLogger extends BaseLogger { } } - async _removeItems(items: QueuedItem[]) { + async _removeItems(items: QueuedItem[]): Promise { const db = await this._openDB(); try { const txn = db.transaction(["logs"], "readwrite"); @@ -186,7 +186,7 @@ class IDBLogExport { /** * @return {Promise} */ - removeFromStore() { + removeFromStore(): Promise { return this._logger._removeItems(this._items); } diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 2909a84e..716a3e73 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -75,18 +75,18 @@ export class LogItem { } /** start a new detached root log item and log a reference to it from this item */ - wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator) { + wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator): void { this.refDetached(this.runDetached(labelOrValues, callback, logLevel, filterCreator)); } /** logs a reference to a different log item, usually obtained from runDetached. This is useful if the referenced operation can't be awaited. */ - refDetached(logItem: LogItem, logLevel: LogLevelOrNull = null) { + refDetached(logItem: LogItem, logLevel: LogLevelOrNull = null): void { logItem.ensureRefId(); this.log({ref: logItem._values.refId as number}, logLevel); } - ensureRefId() { + ensureRefId(): void { if (!this._values.refId) { this.set("refId", this._logger._createRefId()); } @@ -95,7 +95,7 @@ export class LogItem { /** * Creates a new child item and runs it in `callback`. */ - wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null) { + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): unknown { const item = this.child(labelOrValues, logLevel, filterCreator); return item.run(callback); } @@ -136,12 +136,12 @@ export class LogItem { * * Hence, the child item is not returned. */ - log(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull = null) { + log(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull = null): void { const item = this.child(labelOrValues, logLevel, null); item.end = item.start; } - set(key: string | object, value: unknown) { + set(key: string | object, value: unknown): void { if(typeof key === "object") { const values = key; Object.assign(this._values, values); @@ -150,6 +150,7 @@ export class LogItem { } } + // todo: null or undefined here? serialize(filter: LogFilter, parentStartTime: number | null = null, forced: boolean): ISerializedItem | null { if (this._filterCreator) { try { @@ -243,7 +244,7 @@ export class LogItem { * finished the item, recording the end time. After finishing, an item can't be modified anymore as it will be persisted. * @internal shouldn't typically be called by hand. allows to force finish if a promise is still running when closing the app */ - finish() { + finish(): void { if (this.end === null) { if (this._children !== null) { for(const c of this._children) { @@ -281,7 +282,7 @@ export class LogItem { return item; } - get logger() { + get logger(): BaseLogger { return this._logger; } } From 39d0708cca6c8fcbb9e91e398b1eaf0030e1488f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 14 Nov 2021 15:58:51 +0530 Subject: [PATCH 056/174] Add comment --- src/logging/IDBLogger.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 4f6301ce..3283dd0d 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -154,7 +154,8 @@ export class IDBLogger extends BaseLogger { for (const item of items) { const queuedIdx = this._queuedItems.findIndex(i => i.id === item.id); if (queuedIdx === -1) { - logs.delete(item.id); + // todo: isn't id optional? do we need further checks here + logs.delete(item.id!); // resolve questionable use of non-null assertion operator? } else { this._queuedItems.splice(queuedIdx, 1); } From bba44abf529f665ad47e94e56d90c47fe96fba97 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 14 Nov 2021 16:24:16 +0530 Subject: [PATCH 057/174] Convert console logger to ts --- .../{ConsoleLogger.js => ConsoleLogger.ts} | 44 ++++++++++--------- src/logging/LogItem.ts | 10 ++++- 2 files changed, 32 insertions(+), 22 deletions(-) rename src/logging/{ConsoleLogger.js => ConsoleLogger.ts} (58%) diff --git a/src/logging/ConsoleLogger.js b/src/logging/ConsoleLogger.ts similarity index 58% rename from src/logging/ConsoleLogger.js rename to src/logging/ConsoleLogger.ts index 4d3be73b..5b709bb1 100644 --- a/src/logging/ConsoleLogger.js +++ b/src/logging/ConsoleLogger.ts @@ -14,35 +14,37 @@ See the License for the specific language governing permissions and limitations under the License. */ import {BaseLogger} from "./BaseLogger"; +import type {LogItem, LogItemValues} from "./LogItem"; export class ConsoleLogger extends BaseLogger { - _persistItem(item) { + _persistItem(item: LogItem): void { printToConsole(item); } - export() { + export(): void { throw new Error("Cannot export from ConsoleLogger"); } } const excludedKeysFromTable = ["l", "id"]; -function filterValues(values) { +function filterValues(values: LogItemValues): LogItemValues | null { if (!values) { + // todo: is this check here unnecessary because LogItem will always have values? return null; } return Object.entries(values) .filter(([key]) => !excludedKeysFromTable.includes(key)) - .reduce((obj, [key, value]) => { + .reduce((obj: LogItemValues, [key, value]) => { obj = obj || {}; obj[key] = value; return obj; }, null); } -function printToConsole(item) { +function printToConsole(item: LogItem): void { const label = `${itemCaption(item)} (${item.duration}ms)`; - const filteredValues = filterValues(item._values); - const shouldGroup = item._children || filteredValues; + const filteredValues = filterValues(item.values); + const shouldGroup = item.children || filteredValues; if (shouldGroup) { if (item.error) { console.group(label); @@ -62,8 +64,8 @@ function printToConsole(item) { if (filteredValues) { console.table(filteredValues); } - if (item._children) { - for(const c of item._children) { + if (item.children) { + for(const c of item.children) { printToConsole(c); } } @@ -72,18 +74,18 @@ function printToConsole(item) { } } -function itemCaption(item) { - if (item._values.t === "network") { - return `${item._values.method} ${item._values.url}`; - } else if (item._values.l && typeof item._values.id !== "undefined") { - return `${item._values.l} ${item._values.id}`; - } else if (item._values.l && typeof item._values.status !== "undefined") { - return `${item._values.l} (${item._values.status})`; - } else if (item._values.l && item.error) { - return `${item._values.l} failed`; - } else if (typeof item._values.ref !== "undefined") { - return `ref ${item._values.ref}`; +function itemCaption(item: LogItem): string { + if (item.values.t === "network") { + return `${item.values.method} ${item.values.url}`; + } else if (item.values.l && typeof item.values.id !== "undefined") { + return `${item.values.l} ${item.values.id}`; + } else if (item.values.l && typeof item.values.status !== "undefined") { + return `${item.values.l} (${item.values.status})`; + } else if (item.values.l && item.error) { + return `${item.values.l} failed`; + } else if (typeof item.values.ref !== "undefined") { + return `ref ${item.values.ref}`; } else { - return item._values.l || item._values.type; + return item.values.l || item.values.type; } } diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 716a3e73..051b0353 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -33,7 +33,7 @@ interface ISerializedItem { c?: Array; }; -type LogItemValues = { +export type LogItemValues = { l?: string; t?: string; id?: unknown; @@ -285,4 +285,12 @@ export class LogItem { get logger(): BaseLogger { return this._logger; } + + get values(): LogItemValues { + return this._values; + } + + get children(): Array | null { + return this._children; + } } From a7d059b3ed304269296500e6997507b0a3b9ed3e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 14 Nov 2021 19:42:18 +0530 Subject: [PATCH 058/174] Fix imports --- src/matrix/storage/idb/QueryTarget.ts | 2 +- src/matrix/storage/idb/Storage.ts | 2 +- src/matrix/storage/idb/StorageFactory.ts | 4 ++-- src/matrix/storage/idb/Store.ts | 2 +- src/matrix/storage/idb/Transaction.ts | 4 ++-- src/matrix/storage/idb/schema.ts | 2 +- src/matrix/storage/idb/stores/SessionStore.ts | 2 +- src/matrix/storage/idb/stores/TimelineEventStore.ts | 2 +- src/platform/web/Platform.js | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/matrix/storage/idb/QueryTarget.ts b/src/matrix/storage/idb/QueryTarget.ts index 01f46e1a..03fe66bc 100644 --- a/src/matrix/storage/idb/QueryTarget.ts +++ b/src/matrix/storage/idb/QueryTarget.ts @@ -16,7 +16,7 @@ limitations under the License. import {iterateCursor, DONE, NOT_DONE, reqAsPromise} from "./utils"; import {StorageError} from "../common"; -import {LogItem} from "../../../logging/LogItem.js"; +import {LogItem} from "../../../logging/LogItem"; import {IDBKey} from "./Transaction"; // this is the part of the Transaction class API that is used here and in the Store subclass, diff --git a/src/matrix/storage/idb/Storage.ts b/src/matrix/storage/idb/Storage.ts index 53ee7bc0..728d4fca 100644 --- a/src/matrix/storage/idb/Storage.ts +++ b/src/matrix/storage/idb/Storage.ts @@ -18,7 +18,7 @@ import {IDOMStorage} from "./types"; import {Transaction} from "./Transaction"; import { STORE_NAMES, StoreNames, StorageError } from "../common"; import { reqAsPromise } from "./utils"; -import { BaseLogger } from "../../../logging/BaseLogger.js"; +import { BaseLogger } from "../../../logging/BaseLogger"; const WEBKITEARLYCLOSETXNBUG_BOGUS_KEY = "782rh281re38-boguskey"; diff --git a/src/matrix/storage/idb/StorageFactory.ts b/src/matrix/storage/idb/StorageFactory.ts index 4ae55612..f8cc192d 100644 --- a/src/matrix/storage/idb/StorageFactory.ts +++ b/src/matrix/storage/idb/StorageFactory.ts @@ -20,8 +20,8 @@ import { openDatabase, reqAsPromise } from "./utils"; import { exportSession, importSession, Export } from "./export"; import { schema } from "./schema"; import { detectWebkitEarlyCloseTxnBug } from "./quirks"; -import { BaseLogger } from "../../../logging/BaseLogger.js"; -import { LogItem } from "../../../logging/LogItem.js"; +import { BaseLogger } from "../../../logging/BaseLogger"; +import { LogItem } from "../../../logging/LogItem"; const sessionName = (sessionId: string) => `hydrogen_session_${sessionId}`; const openDatabaseWithSessionId = function(sessionId: string, idbFactory: IDBFactory, localStorage: IDOMStorage, log: LogItem) { diff --git a/src/matrix/storage/idb/Store.ts b/src/matrix/storage/idb/Store.ts index a4c80426..74de2afa 100644 --- a/src/matrix/storage/idb/Store.ts +++ b/src/matrix/storage/idb/Store.ts @@ -18,7 +18,7 @@ import {QueryTarget, IDBQuery, ITransaction} from "./QueryTarget"; import {IDBRequestError, IDBRequestAttemptError} from "./error"; import {reqAsPromise} from "./utils"; import {Transaction, IDBKey} from "./Transaction"; -import {LogItem} from "../../../logging/LogItem.js"; +import {LogItem} from "../../../logging/LogItem"; const LOG_REQUESTS = false; diff --git a/src/matrix/storage/idb/Transaction.ts b/src/matrix/storage/idb/Transaction.ts index 3bc4aed2..0d9e55a5 100644 --- a/src/matrix/storage/idb/Transaction.ts +++ b/src/matrix/storage/idb/Transaction.ts @@ -36,8 +36,8 @@ import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore"; import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore"; import {OperationStore} from "./stores/OperationStore"; import {AccountDataStore} from "./stores/AccountDataStore"; -import {LogItem} from "../../../logging/LogItem.js"; -import {BaseLogger} from "../../../logging/BaseLogger.js"; +import {LogItem} from "../../../logging/LogItem"; +import {BaseLogger} from "../../../logging/BaseLogger"; export type IDBKey = IDBValidKey | IDBKeyRange; diff --git a/src/matrix/storage/idb/schema.ts b/src/matrix/storage/idb/schema.ts index cd22d803..a9c4e2e4 100644 --- a/src/matrix/storage/idb/schema.ts +++ b/src/matrix/storage/idb/schema.ts @@ -11,7 +11,7 @@ import {SessionStore} from "./stores/SessionStore"; import {Store} from "./Store"; import {encodeScopeTypeKey} from "./stores/OperationStore"; import {MAX_UNICODE} from "./stores/common"; -import {LogItem} from "../../../logging/LogItem.js"; +import {LogItem} from "../../../logging/LogItem"; export type MigrationFunc = (db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: LogItem) => Promise | void; diff --git a/src/matrix/storage/idb/stores/SessionStore.ts b/src/matrix/storage/idb/stores/SessionStore.ts index 785835b8..6bf87e9b 100644 --- a/src/matrix/storage/idb/stores/SessionStore.ts +++ b/src/matrix/storage/idb/stores/SessionStore.ts @@ -16,7 +16,7 @@ limitations under the License. import {Store} from "../Store"; import {IDOMStorage} from "../types"; import {SESSION_E2EE_KEY_PREFIX} from "../../../e2ee/common.js"; -import {LogItem} from "../../../../logging/LogItem.js"; +import {LogItem} from "../../../../logging/LogItem"; import {parse, stringify} from "../../../../utils/typedJSON"; export interface SessionEntry { diff --git a/src/matrix/storage/idb/stores/TimelineEventStore.ts b/src/matrix/storage/idb/stores/TimelineEventStore.ts index 2087a39f..3c101701 100644 --- a/src/matrix/storage/idb/stores/TimelineEventStore.ts +++ b/src/matrix/storage/idb/stores/TimelineEventStore.ts @@ -20,7 +20,7 @@ import { encodeUint32, decodeUint32 } from "../utils"; import {KeyLimits} from "../../common"; import {Store} from "../Store"; import {TimelineEvent, StateEvent} from "../../types"; -import {LogItem} from "../../../../logging/LogItem.js"; +import {LogItem} from "../../../../logging/LogItem"; interface Annotation { count: number; diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 63cbac2e..f6cd2895 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -22,7 +22,7 @@ import {SettingsStorage} from "./dom/SettingsStorage.js"; import {Encoding} from "./utils/Encoding.js"; import {OlmWorker} from "../../matrix/e2ee/OlmWorker.js"; import {IDBLogger} from "../../logging/IDBLogger"; -import {ConsoleLogger} from "../../logging/ConsoleLogger.js"; +import {ConsoleLogger} from "../../logging/ConsoleLogger"; import {RootView} from "./ui/RootView.js"; import {Clock} from "./dom/Clock.js"; import {ServiceWorkerHandler} from "./dom/ServiceWorkerHandler.js"; From 520e0f1b892299f98d31a6f05157d4b5d82afdb6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 15 Nov 2021 17:29:08 +0530 Subject: [PATCH 059/174] Use interface ILogItem --- .../room/timeline/ReactionsViewModel.js | 2 +- src/logging/BaseLogger.ts | 12 +-- src/logging/ConsoleLogger.ts | 8 +- src/logging/IDBLogger.ts | 6 +- src/logging/LogFilter.ts | 4 +- src/logging/LogItem.ts | 38 ++++++-- src/logging/{NullLogger.js => NullLogger.ts} | 90 ++++++++++--------- src/logging/utils.js | 2 +- src/matrix/e2ee/megolm/Decryption.ts | 6 +- src/matrix/room/Invite.js | 2 +- src/matrix/room/sending/SendQueue.js | 2 +- src/matrix/room/timeline/Timeline.js | 2 +- .../room/timeline/persistence/GapWriter.js | 2 +- .../timeline/persistence/RelationWriter.js | 2 +- .../room/timeline/persistence/SyncWriter.js | 2 +- src/matrix/storage/idb/QueryTarget.ts | 6 +- src/matrix/storage/idb/StorageFactory.ts | 31 ++++--- src/matrix/storage/idb/Store.ts | 12 +-- src/matrix/storage/idb/Transaction.ts | 14 +-- src/matrix/storage/idb/schema.ts | 12 +-- src/matrix/storage/idb/stores/SessionStore.ts | 4 +- .../storage/idb/stores/TimelineEventStore.ts | 8 +- src/mocks/Storage.ts | 2 +- 23 files changed, 155 insertions(+), 114 deletions(-) rename src/logging/{NullLogger.js => NullLogger.ts} (57%) diff --git a/src/domain/session/room/timeline/ReactionsViewModel.js b/src/domain/session/room/timeline/ReactionsViewModel.js index 25d74b49..3a47aaa0 100644 --- a/src/domain/session/room/timeline/ReactionsViewModel.js +++ b/src/domain/session/room/timeline/ReactionsViewModel.js @@ -184,7 +184,7 @@ import {Clock as MockClock} from "../../../../mocks/Clock.js"; import {createMockStorage} from "../../../../mocks/Storage"; import {ListObserver} from "../../../../mocks/ListObserver.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"; // other imports import {BaseMessageTile} from "./tiles/BaseMessageTile.js"; diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 9865355a..e420c0df 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -17,14 +17,14 @@ limitations under the License. import {LogItem} from "./LogItem"; 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"; // 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"; export abstract class BaseLogger { - protected _openItems: Set = new Set(); + protected _openItems: Set = new Set(); protected _platform: 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. */ - 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) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { @@ -51,7 +51,7 @@ export abstract class BaseLogger { Useful to pair with 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? if (logLevel === null) { logLevel = LogLevel.Info; @@ -72,7 +72,7 @@ export abstract class BaseLogger { 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); const finishItem = () => { @@ -135,7 +135,7 @@ export abstract class BaseLogger { this._openItems.clear(); } - abstract _persistItem(item: LogItem, filter?: LogFilter, forced?: boolean): void; + abstract _persistItem(item: ILogItem, filter?: LogFilter, forced?: boolean): void; abstract export(): void; diff --git a/src/logging/ConsoleLogger.ts b/src/logging/ConsoleLogger.ts index 5b709bb1..d8693e38 100644 --- a/src/logging/ConsoleLogger.ts +++ b/src/logging/ConsoleLogger.ts @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ import {BaseLogger} from "./BaseLogger"; -import type {LogItem, LogItemValues} from "./LogItem"; +import type {ILogItem, LogItemValues} from "./LogItem"; export class ConsoleLogger extends BaseLogger { - _persistItem(item: LogItem): void { + _persistItem(item: ILogItem): void { printToConsole(item); } @@ -41,7 +41,7 @@ function filterValues(values: LogItemValues): LogItemValues | null { }, null); } -function printToConsole(item: LogItem): void { +function printToConsole(item: ILogItem): void { const label = `${itemCaption(item)} (${item.duration}ms)`; const filteredValues = filterValues(item.values); 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") { return `${item.values.method} ${item.values.url}`; } else if (item.values.l && typeof item.values.id !== "undefined") { diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 3283dd0d..960db0da 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -26,7 +26,7 @@ import {BaseLogger} from "./BaseLogger"; import type {Interval} from "../platform/web/dom/Clock"; import type {Platform} from "../platform/web/Platform.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"; type QueuedItem = { @@ -116,8 +116,8 @@ export class IDBLogger extends BaseLogger { return openDatabase(this._name, db => db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}), 1); } - _persistItem(logItem: LogItem, filter: LogFilter, forced: boolean): void { - const serializedItem = logItem.serialize(filter, undefined, forced); + _persistItem(logItem: ILogItem, filter: LogFilter, forced: boolean): void { + const serializedItem = logItem.serialize(filter, null, forced); this._queuedItems.push({ json: JSON.stringify(serializedItem) }); diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index 480e92db..dc415ede 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import type {LogItem} from "./LogItem"; +import type {ILogItem} from "./LogItem"; export enum LogLevel { All = 1, @@ -37,7 +37,7 @@ export class LogFilter { this._parentFilter = parentFilter; } - filter(item: LogItem, children: Array | null): boolean { + filter(item: ILogItem, children: Array | null): boolean { if (this._parentFilter) { if (!this._parentFilter.filter(item, children)) { return false; diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 051b0353..54957347 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -33,6 +33,30 @@ interface ISerializedItem { c?: Array; }; +export interface ILogItem { + logger: any; + level: typeof LogLevel; + duration?: number; + end?: number | null; + start?: number; + logLevel: LogLevel; + children: Array | 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 = { l?: string; t?: string; @@ -44,10 +68,10 @@ export type LogItemValues = { } export type LabelOrValues = string | LogItemValues; -export type FilterCreator = ((filter: LogFilter, item: LogItem) => LogFilter) | null; -export type LogCallback = (item: LogItem) => unknown; +export type FilterCreator = ((filter: LogFilter, item: ILogItem) => LogFilter) | null; +export type LogCallback = (item: ILogItem) => unknown; -export class LogItem { +export class LogItem implements ILogItem { public readonly start: number; public logLevel: LogLevel; 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 */ - 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); } @@ -81,9 +105,9 @@ export class LogItem { /** logs a reference to a different log item, usually obtained from runDetached. 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(); - this.log({ref: logItem._values.refId as number}, logLevel); + this.log({ref: (logItem as LogItem)._values.refId}, logLevel); } ensureRefId(): void { @@ -267,7 +291,7 @@ export class LogItem { return err; } - child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator): LogItem { + child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator): ILogItem { if (this.end !== null) { console.trace("log item is finished, additional logs will likely not be recorded"); } diff --git a/src/logging/NullLogger.js b/src/logging/NullLogger.ts similarity index 57% rename from src/logging/NullLogger.js rename to src/logging/NullLogger.ts index 12eec5c9..285ea1e4 100644 --- a/src/logging/NullLogger.js +++ b/src/logging/NullLogger.ts @@ -14,18 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ import {LogLevel} from "./LogFilter"; +import type {ILogItem, LabelOrValues, LogCallback, LogItemValues} from "./LogItem"; -function noop () {} +function noop (): void {} export class NullLogger { - constructor() { - this.item = new NullLogItem(this); - } + public readonly item: ILogItem = new NullLogItem(this); - log() {} + log(): void {} - run(_, callback) { + run(_: null, callback) { return callback(this.item); } @@ -50,50 +49,61 @@ export class NullLogger { } } -export class NullLogItem { - constructor(logger) { +export class NullLogItem implements ILogItem { + public readonly logger: NullLogger; + public readonly logLevel: LogLevel; + public children: Array | null = null; + public values: LogItemValues; + public error: Error | null = null; + + constructor(logger: NullLogger) { this.logger = logger; } - wrap(_, callback) { + wrap(_: LabelOrValues, callback: LogCallback): unknown { return callback(this); } - log() {} - set() {} + log(): void {} + set(): void {} - runDetached(_, callback) { + runDetached(_: LabelOrValues, callback: LogCallback): ILogItem { 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; } - 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(); diff --git a/src/logging/utils.js b/src/logging/utils.js index 3da9aa5d..659df055 100644 --- a/src/logging/utils.js +++ b/src/logging/utils.js @@ -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) // 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) { if (logItem) { diff --git a/src/matrix/e2ee/megolm/Decryption.ts b/src/matrix/e2ee/megolm/Decryption.ts index 842d423d..4cb66971 100644 --- a/src/matrix/e2ee/megolm/Decryption.ts +++ b/src/matrix/e2ee/megolm/Decryption.ts @@ -26,7 +26,7 @@ import type {OlmWorker} from "../OlmWorker"; import type {Transaction} from "../../storage/idb/Transaction"; import type {TimelineEvent} from "../../storage/types"; import type {DecryptionResult} from "../DecryptionResult"; -import type {LogItem} from "../../../logging/LogItem"; +import type {ILogItem} from "../../../logging/LogItem"; export class Decryption { private keyLoader: KeyLoader; @@ -136,7 +136,7 @@ export class Decryption { * Extracts room keys from decrypted device messages. * 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[] = []; for (const dr of decryptionResults) { 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.set("invalid", true); } - }, log.level.Detail); + }, log.level.Detail, null); } return keys; } diff --git a/src/matrix/room/Invite.js b/src/matrix/room/Invite.js index 1bf802dd..da721c77 100644 --- a/src/matrix/room/Invite.js +++ b/src/matrix/room/Invite.js @@ -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 {default as roomInviteFixture} from "../../fixtures/matrix/invites/room.js"; import {default as dmInviteFixture} from "../../fixtures/matrix/invites/dm.js"; diff --git a/src/matrix/room/sending/SendQueue.js b/src/matrix/room/sending/SendQueue.js index 28408e34..896100c7 100644 --- a/src/matrix/room/sending/SendQueue.js +++ b/src/matrix/room/sending/SendQueue.js @@ -353,7 +353,7 @@ export class SendQueue { import {HomeServer as MockHomeServer} from "../../../mocks/HomeServer.js"; import {createMockStorage} from "../../../mocks/Storage"; 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 {poll} from "../../../mocks/poll.js"; import {createAnnotation} from "../timeline/relations.js"; diff --git a/src/matrix/room/timeline/Timeline.js b/src/matrix/room/timeline/Timeline.js index 4e6fe40c..bd89ee8d 100644 --- a/src/matrix/room/timeline/Timeline.js +++ b/src/matrix/room/timeline/Timeline.js @@ -346,7 +346,7 @@ import {Clock as MockClock} from "../../../mocks/Clock.js"; import {createMockStorage} from "../../../mocks/Storage"; import {ListObserver} from "../../../mocks/ListObserver.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 {User} from "../../User.js"; import {PendingEvent} from "../sending/PendingEvent.js"; diff --git a/src/matrix/room/timeline/persistence/GapWriter.js b/src/matrix/room/timeline/persistence/GapWriter.js index 7794b797..3e520608 100644 --- a/src/matrix/room/timeline/persistence/GapWriter.js +++ b/src/matrix/room/timeline/persistence/GapWriter.js @@ -205,7 +205,7 @@ import {FragmentIdComparer} from "../FragmentIdComparer.js"; import {RelationWriter} from "./RelationWriter.js"; import {createMockStorage} from "../../../../mocks/Storage"; 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 {SyncWriter} from "./SyncWriter.js"; import {MemberWriter} from "./MemberWriter.js"; diff --git a/src/matrix/room/timeline/persistence/RelationWriter.js b/src/matrix/room/timeline/persistence/RelationWriter.js index c0d3d369..60a2b618 100644 --- a/src/matrix/room/timeline/persistence/RelationWriter.js +++ b/src/matrix/room/timeline/persistence/RelationWriter.js @@ -257,7 +257,7 @@ import {createMockStorage} from "../../../../mocks/Storage"; import {createEvent, withTextBody, withRedacts, withContent} from "../../../../mocks/event.js"; import {createAnnotation} from "../relations.js"; import {FragmentIdComparer} from "../FragmentIdComparer.js"; -import {NullLogItem} from "../../../../logging/NullLogger.js"; +import {NullLogItem} from "../../../../logging/NullLogger"; export function tests() { const fragmentIdComparer = new FragmentIdComparer([]); diff --git a/src/matrix/room/timeline/persistence/SyncWriter.js b/src/matrix/room/timeline/persistence/SyncWriter.js index 749c3392..dc5ae3a8 100644 --- a/src/matrix/room/timeline/persistence/SyncWriter.js +++ b/src/matrix/room/timeline/persistence/SyncWriter.js @@ -258,7 +258,7 @@ export class SyncWriter { import {createMockStorage} from "../../../../mocks/Storage"; 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() { const roomId = "!abc:hs.tld"; return { diff --git a/src/matrix/storage/idb/QueryTarget.ts b/src/matrix/storage/idb/QueryTarget.ts index 03fe66bc..7519beac 100644 --- a/src/matrix/storage/idb/QueryTarget.ts +++ b/src/matrix/storage/idb/QueryTarget.ts @@ -16,7 +16,7 @@ limitations under the License. import {iterateCursor, DONE, NOT_DONE, reqAsPromise} from "./utils"; import {StorageError} from "../common"; -import {LogItem} from "../../../logging/LogItem"; +import {ILogItem} from "../../../logging/LogItem"; import {IDBKey} from "./Transaction"; // 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; IDBKeyRange: typeof IDBKeyRange; 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 = (acc: B, val: A) => B @@ -277,7 +277,7 @@ export function tests() { class MockTransaction extends MockIDBImpl { 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 { diff --git a/src/matrix/storage/idb/StorageFactory.ts b/src/matrix/storage/idb/StorageFactory.ts index f8cc192d..5ca6bce4 100644 --- a/src/matrix/storage/idb/StorageFactory.ts +++ b/src/matrix/storage/idb/StorageFactory.ts @@ -21,10 +21,10 @@ import { exportSession, importSession, Export } from "./export"; import { schema } from "./schema"; import { detectWebkitEarlyCloseTxnBug } from "./quirks"; import { BaseLogger } from "../../../logging/BaseLogger"; -import { LogItem } from "../../../logging/LogItem"; +import { ILogItem } from "../../../logging/LogItem"; 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); return openDatabase(sessionName(sessionId), create, schema.length, idbFactory); } @@ -63,7 +63,7 @@ export class StorageFactory { this._localStorage = localStorage; } - async create(sessionId: string, log: LogItem): Promise { + async create(sessionId: string, log: ILogItem): Promise { await this._serviceWorkerHandler?.preventConcurrentSessionAccess(sessionId); requestPersistedStorage().then(persisted => { // Firefox lies here though, and returns true even if the user denied the request @@ -83,23 +83,30 @@ export class StorageFactory { return reqAsPromise(req); } - async export(sessionId: string, log: LogItem): Promise { + async export(sessionId: string, log: ILogItem): Promise { const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log); return await exportSession(db); } - async import(sessionId: string, data: Export, log: LogItem): Promise { + async import(sessionId: string, data: Export, log: ILogItem): Promise { const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log); return await importSession(db, data); } } -async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: number | null, version: number, localStorage: IDOMStorage, log: LogItem): Promise { +async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: number | null, version: number, localStorage: IDOMStorage, log: ILogItem): Promise { const startIdx = oldVersion || 0; - return log.wrap({l: "storage migration", oldVersion, version}, async log => { - for(let i = startIdx; i < version; ++i) { - const migrationFunc = schema[i]; - await log.wrap(`v${i + 1}`, log => migrationFunc(db, txn, localStorage, log)); - } - }) as Promise; + return log.wrap( + { l: "storage migration", oldVersion, version }, + async (log) => { + for (let i = startIdx; i < version; ++i) { + const migrationFunc = schema[i]; + await log.wrap(`v${i + 1}`, (log) => + migrationFunc(db, txn, localStorage, log), null, null + ); + } + }, + null, + null + ) as Promise; } diff --git a/src/matrix/storage/idb/Store.ts b/src/matrix/storage/idb/Store.ts index 74de2afa..5a2a9abc 100644 --- a/src/matrix/storage/idb/Store.ts +++ b/src/matrix/storage/idb/Store.ts @@ -18,7 +18,7 @@ import {QueryTarget, IDBQuery, ITransaction} from "./QueryTarget"; import {IDBRequestError, IDBRequestAttemptError} from "./error"; import {reqAsPromise} from "./utils"; import {Transaction, IDBKey} from "./Transaction"; -import {LogItem} from "../../../logging/LogItem"; +import {ILogItem} from "../../../logging/LogItem"; const LOG_REQUESTS = false; @@ -145,7 +145,7 @@ export class Store extends QueryTarget { return new QueryTarget(new QueryTargetWrapper(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, // which is the behaviour we want. Therefore, it is ok to not create a promise for this // request and await it. @@ -160,13 +160,13 @@ export class Store extends QueryTarget { 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`. const request = this._idbStore.add(value); this._prepareErrorLog(request, log, "add", undefined, value); } - async tryAdd(value: T, log: LogItem): Promise { + async tryAdd(value: T, log: ILogItem): Promise { try { await reqAsPromise(this._idbStore.add(value)); return true; @@ -181,13 +181,13 @@ export class Store extends QueryTarget { } } - 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`. const request = this._idbStore.delete(keyOrKeyRange); 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) { log.ensureRefId(); } diff --git a/src/matrix/storage/idb/Transaction.ts b/src/matrix/storage/idb/Transaction.ts index 0d9e55a5..721ee711 100644 --- a/src/matrix/storage/idb/Transaction.ts +++ b/src/matrix/storage/idb/Transaction.ts @@ -36,7 +36,7 @@ import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore"; import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore"; import {OperationStore} from "./stores/OperationStore"; import {AccountDataStore} from "./stores/AccountDataStore"; -import {LogItem} from "../../../logging/LogItem"; +import {ILogItem} from "../../../logging/LogItem"; import {BaseLogger} from "../../../logging/BaseLogger"; export type IDBKey = IDBValidKey | IDBKeyRange; @@ -44,7 +44,7 @@ export type IDBKey = IDBValidKey | IDBKeyRange; class WriteErrorInfo { constructor( public readonly error: StorageError, - public readonly refItem: LogItem | undefined, + public readonly refItem: ILogItem | undefined, public readonly operationName: string, public readonly keys: IDBKey[] | undefined, ) {} @@ -169,7 +169,7 @@ export class Transaction { return this._store(StoreNames.accountData, idbStore => new AccountDataStore(idbStore)); } - async complete(log?: LogItem): Promise { + async complete(log?: ILogItem): Promise { try { await txnAsPromise(this._txn); } catch (err) { @@ -190,7 +190,7 @@ export class Transaction { return error; } - abort(log?: LogItem): void { + abort(log?: ILogItem): void { // TODO: should we wrap the exception in a StorageError? try { 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 if (error.errcode !== "AbortError" || this._writeErrors.length === 0) { this._writeErrors.push(new WriteErrorInfo(error, refItem, operationName, keys)); } } - private _logWriteErrors(parentItem: LogItem | undefined) { + private _logWriteErrors(parentItem: ILogItem | undefined) { const callback = errorGroupItem => { // we don't have context when there is no parentItem, so at least log stores if (!parentItem) { @@ -226,7 +226,7 @@ export class Transaction { }; const label = `${this._writeErrors.length} storage write operation(s) failed`; if (parentItem) { - parentItem.wrap(label, callback); + parentItem.wrap(label, callback, null, null); } else { this.logger.run(label, callback); } diff --git a/src/matrix/storage/idb/schema.ts b/src/matrix/storage/idb/schema.ts index a9c4e2e4..62a4cea1 100644 --- a/src/matrix/storage/idb/schema.ts +++ b/src/matrix/storage/idb/schema.ts @@ -11,10 +11,10 @@ import {SessionStore} from "./stores/SessionStore"; import {Store} from "./Store"; import {encodeScopeTypeKey} from "./stores/OperationStore"; 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; +export type MigrationFunc = (db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem) => Promise | void; // FUNCTIONS SHOULD ONLY BE APPENDED!! // the index in the array is the database version 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) -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 trackedRoomIds: string[] = []; await iterateCursor(roomSummaryStore.openCursor(), roomSummary => { @@ -196,7 +196,7 @@ async function fixMissingRoomsInUserIdentities(db: IDBDatabase, txn: IDBTransact const updatedIdentity = addRoomToIdentity(identity, userId, roomId); if (updatedIdentity) { log.log({l: `fixing up`, id: userId, - roomsBefore: originalRoomCount, roomsAfter: updatedIdentity.roomIds.length}); + roomsBefore: originalRoomCount, roomsAfter: updatedIdentity.roomIds.length}, null); userIdentitiesStore.put(updatedIdentity); 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 outboundGroupSessionsStore.delete(roomId); } - }); + }, null, null); } } @@ -220,7 +220,7 @@ async function changeSSSSKeyPrefix(db: IDBDatabase, txn: IDBTransaction) { } } // 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"); // 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. diff --git a/src/matrix/storage/idb/stores/SessionStore.ts b/src/matrix/storage/idb/stores/SessionStore.ts index 6bf87e9b..dd133b45 100644 --- a/src/matrix/storage/idb/stores/SessionStore.ts +++ b/src/matrix/storage/idb/stores/SessionStore.ts @@ -16,7 +16,7 @@ limitations under the License. import {Store} from "../Store"; import {IDOMStorage} from "../types"; 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"; export interface SessionEntry { @@ -64,7 +64,7 @@ export class SessionStore { }); } - async tryRestoreE2EEIdentityFromLocalStorage(log: LogItem): Promise { + async tryRestoreE2EEIdentityFromLocalStorage(log: ILogItem): Promise { let success = false; const lsPrefix = this._localStorageKeyPrefix; const prefix = lsPrefix + SESSION_E2EE_KEY_PREFIX; diff --git a/src/matrix/storage/idb/stores/TimelineEventStore.ts b/src/matrix/storage/idb/stores/TimelineEventStore.ts index 3c101701..b3663c29 100644 --- a/src/matrix/storage/idb/stores/TimelineEventStore.ts +++ b/src/matrix/storage/idb/stores/TimelineEventStore.ts @@ -20,7 +20,7 @@ import { encodeUint32, decodeUint32 } from "../utils"; import {KeyLimits} from "../../common"; import {Store} from "../Store"; import {TimelineEvent, StateEvent} from "../../types"; -import {LogItem} from "../../../../logging/LogItem"; +import {ILogItem} from "../../../../logging/LogItem"; interface Annotation { count: number; @@ -286,7 +286,7 @@ export class TimelineEventStore { * * Returns if the event was not yet known and the entry was written. */ - tryInsert(entry: TimelineEventEntry, log: LogItem): Promise { + tryInsert(entry: TimelineEventEntry, log: ILogItem): Promise { (entry as TimelineEventStorageEntry).key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex); (entry as TimelineEventStorageEntry).eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id); return this._timelineStore.tryAdd(entry as TimelineEventStorageEntry, log); @@ -320,7 +320,7 @@ export class TimelineEventStore { import {createMockStorage} from "../../../../mocks/Storage"; import {createEvent, withTextBody} from "../../../../mocks/event.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() { @@ -368,7 +368,7 @@ export function tests() { let eventKey = EventKey.defaultFragmentKey(109); for (const insertedId of insertedIds) { 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(); } const eventKeyMap = await txn.timelineEvents.getEventKeysForIds(roomId, checkedIds); diff --git a/src/mocks/Storage.ts b/src/mocks/Storage.ts index 5dba796a..a5a30ffd 100644 --- a/src/mocks/Storage.ts +++ b/src/mocks/Storage.ts @@ -18,7 +18,7 @@ import {FDBFactory, FDBKeyRange} from "../../lib/fake-indexeddb/index.js"; import {StorageFactory} from "../matrix/storage/idb/StorageFactory"; import {IDOMStorage} from "../matrix/storage/idb/types"; 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"; export function createMockStorage(): Promise { From 30a384fe1e8ddcd7fe627cac55a663e7c6d7e8ea Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 15 Nov 2021 18:44:25 +0530 Subject: [PATCH 060/174] Make LogFilter optional --- src/logging/BaseLogger.ts | 18 +++++++++--------- src/logging/LogItem.ts | 16 ++++++++-------- src/matrix/e2ee/megolm/Decryption.ts | 2 +- src/matrix/storage/idb/StorageFactory.ts | 8 ++------ src/matrix/storage/idb/Transaction.ts | 2 +- src/matrix/storage/idb/schema.ts | 2 +- 6 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index e420c0df..28602b17 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -32,13 +32,13 @@ export abstract class BaseLogger { } log(labelOrValues: LabelOrValues, logLevel: LogLevel = LogLevel.Info) { - const item = new LogItem(labelOrValues, logLevel, null, this); + const item = new LogItem(labelOrValues, logLevel, this); item.end = item.start; this._persistItem(item, undefined, false); } /** if item is a log item, wrap the callback in a child of it, otherwise start a new root log item. */ - wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): unknown { + wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator?: FilterCreator): unknown { if (item) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { @@ -51,28 +51,28 @@ export abstract class BaseLogger { Useful to pair with 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): ILogItem { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator?: FilterCreator): ILogItem { // todo: Remove jsdoc type? if (logLevel === null) { logLevel = LogLevel.Info; } - const item = new LogItem(labelOrValues, logLevel, null, this); - this._run(item, callback, logLevel!, filterCreator, false /* don't throw, nobody is awaiting */); + const item = new LogItem(labelOrValues, logLevel, this); + this._run(item, callback, logLevel!, false /* don't throw, nobody is awaiting */, filterCreator); return item; } /** run a callback wrapped in a log operation. Errors and duration are transparently logged, also for async operations. Whatever the callback returns is returned here. */ - run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): unknown { + run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator?: FilterCreator): unknown { if (logLevel === null) { logLevel = LogLevel.Info; } - const item = new LogItem(labelOrValues, logLevel, null, this); - return this._run(item, callback, logLevel!, filterCreator, true); + const item = new LogItem(labelOrValues, logLevel, this); + return this._run(item, callback, logLevel!, true, filterCreator); } - _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, filterCreator: FilterCreator, shouldThrow: boolean): unknown { + _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, shouldThrow: boolean, filterCreator?: FilterCreator): unknown { this._openItems.add(item); const finishItem = () => { diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 54957347..c40d7c08 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -43,7 +43,7 @@ export interface ILogItem { children: Array | null; values: LogItemValues; error: Error | null; - wrap(labelOrValues: LabelOrValues, callback: LogCallback, level: LogLevelOrNull, filterCreator: FilterCreator): unknown; + 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; @@ -68,7 +68,7 @@ export type LogItemValues = { } export type LabelOrValues = string | LogItemValues; -export type FilterCreator = ((filter: LogFilter, item: ILogItem) => LogFilter) | null; +export type FilterCreator = ((filter: LogFilter, item: ILogItem) => LogFilter); export type LogCallback = (item: ILogItem) => unknown; export class LogItem implements ILogItem { @@ -78,10 +78,10 @@ export class LogItem implements ILogItem { public end: number | null; private _values: LogItemValues; private _logger: BaseLogger; - private _filterCreator: FilterCreator; + private _filterCreator?: FilterCreator; private _children: Array | null; - constructor(labelOrValues: LabelOrValues, logLevel: LogLevel, filterCreator: FilterCreator, logger: BaseLogger) { + constructor(labelOrValues: LabelOrValues, logLevel: LogLevel, logger: BaseLogger, filterCreator?: FilterCreator) { this._logger = logger; this.start = logger._now(); this.end = null; @@ -119,7 +119,7 @@ export class LogItem implements ILogItem { /** * Creates a new child item and runs it in `callback`. */ - wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator: FilterCreator = null): unknown { + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator?: FilterCreator): unknown { const item = this.child(labelOrValues, logLevel, filterCreator); return item.run(callback); } @@ -161,7 +161,7 @@ export class LogItem implements ILogItem { * Hence, the child item is not returned. */ log(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull = null): void { - const item = this.child(labelOrValues, logLevel, null); + const item = this.child(labelOrValues, logLevel); item.end = item.start; } @@ -291,14 +291,14 @@ export class LogItem implements ILogItem { return err; } - child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator): ILogItem { + child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator?: FilterCreator): ILogItem { if (this.end !== null) { console.trace("log item is finished, additional logs will likely not be recorded"); } if (!logLevel) { logLevel = this.logLevel || LogLevel.Info; } - const item = new LogItem(labelOrValues, logLevel, filterCreator, this._logger); + const item = new LogItem(labelOrValues, logLevel, this._logger, filterCreator); if (this._children === null) { this._children = []; } diff --git a/src/matrix/e2ee/megolm/Decryption.ts b/src/matrix/e2ee/megolm/Decryption.ts index 4cb66971..ee96eca1 100644 --- a/src/matrix/e2ee/megolm/Decryption.ts +++ b/src/matrix/e2ee/megolm/Decryption.ts @@ -152,7 +152,7 @@ export class Decryption { log.logLevel = log.level.Warn; log.set("invalid", true); } - }, log.level.Detail, null); + }, log.level.Detail); } return keys; } diff --git a/src/matrix/storage/idb/StorageFactory.ts b/src/matrix/storage/idb/StorageFactory.ts index 5ca6bce4..5c257bfb 100644 --- a/src/matrix/storage/idb/StorageFactory.ts +++ b/src/matrix/storage/idb/StorageFactory.ts @@ -101,12 +101,8 @@ async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: nu async (log) => { for (let i = startIdx; i < version; ++i) { const migrationFunc = schema[i]; - await log.wrap(`v${i + 1}`, (log) => - migrationFunc(db, txn, localStorage, log), null, null - ); + await log.wrap(`v${i + 1}`, (log) => migrationFunc(db, txn, localStorage, log), null); } }, - null, - null - ) as Promise; + null) as Promise; } diff --git a/src/matrix/storage/idb/Transaction.ts b/src/matrix/storage/idb/Transaction.ts index 721ee711..c68a1759 100644 --- a/src/matrix/storage/idb/Transaction.ts +++ b/src/matrix/storage/idb/Transaction.ts @@ -226,7 +226,7 @@ export class Transaction { }; const label = `${this._writeErrors.length} storage write operation(s) failed`; if (parentItem) { - parentItem.wrap(label, callback, null, null); + parentItem.wrap(label, callback, null); } else { this.logger.run(label, callback); } diff --git a/src/matrix/storage/idb/schema.ts b/src/matrix/storage/idb/schema.ts index 62a4cea1..b21bd30f 100644 --- a/src/matrix/storage/idb/schema.ts +++ b/src/matrix/storage/idb/schema.ts @@ -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 outboundGroupSessionsStore.delete(roomId); } - }, null, null); + }, null); } } From 7097ba07d168d1a4156099dd9967310aebfa415a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 15 Nov 2021 18:59:33 +0530 Subject: [PATCH 061/174] Replace LogLabelOrNull type with undefined --- src/logging/BaseLogger.ts | 11 +++++------ src/logging/IDBLogger.ts | 1 + src/logging/LogFilter.ts | 2 -- src/logging/LogItem.ts | 25 ++++++++++++------------ src/matrix/storage/idb/StorageFactory.ts | 5 ++--- src/matrix/storage/idb/Transaction.ts | 2 +- src/matrix/storage/idb/schema.ts | 4 ++-- 7 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 28602b17..6c377d48 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -18,7 +18,6 @@ limitations under the License. import {LogItem} from "./LogItem"; import {LogLevel, LogFilter} from "./LogFilter"; import type {FilterCreator, LabelOrValues, LogCallback, ILogItem} from "./LogItem"; -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? import type {Platform} from "../platform/web/Platform.js"; @@ -38,7 +37,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. */ - wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator?: FilterCreator): unknown { + wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): unknown { if (item) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { @@ -51,9 +50,9 @@ export abstract class BaseLogger { Useful to pair with LogItem.refDetached. @return {LogItem} the log item added, useful to pass to LogItem.refDetached */ - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator?: FilterCreator): ILogItem { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { // todo: Remove jsdoc type? - if (logLevel === null) { + if (!logLevel) { logLevel = LogLevel.Info; } const item = new LogItem(labelOrValues, logLevel, this); @@ -64,8 +63,8 @@ export abstract class BaseLogger { /** run a callback wrapped in a log operation. Errors and duration are transparently logged, also for async operations. Whatever the callback returns is returned here. */ - run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator?: FilterCreator): unknown { - if (logLevel === null) { + run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): unknown { + if (!logLevel) { logLevel = LogLevel.Info; } const item = new LogItem(labelOrValues, logLevel, this); diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 960db0da..d5ce2f9a 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -40,6 +40,7 @@ export class IDBLogger extends BaseLogger { private readonly _flushInterval: Interval; private _queuedItems: QueuedItem[]; + // todo: type constructor constructor(options) { super(options); const {name, flushInterval = 60 * 1000, limit = 3000} = options; diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index dc415ede..9968e63e 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -27,8 +27,6 @@ export enum LogLevel { Off } -export type LogLevelOrNull = LogLevel | null; - export class LogFilter { private _min?: LogLevel; private _parentFilter?: LogFilter; diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index c40d7c08..c0325ca7 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -16,7 +16,6 @@ limitations under the License. */ import {LogLevel, LogFilter} from "./LogFilter"; -import type {LogLevelOrNull} from "./LogFilter"; import type {BaseLogger} from "./BaseLogger"; interface ISerializedItem { @@ -43,17 +42,17 @@ export interface ILogItem { children: Array | null; values: LogItemValues; error: Error | null; - wrap(labelOrValues: LabelOrValues, callback: LogCallback, level: LogLevelOrNull, filterCreator?: FilterCreator): unknown; - log(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull): void; + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): unknown; + log(labelOrValues: LabelOrValues, logLevel?: LogLevel): 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; + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; + wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void; + refDetached(logItem: ILogItem, logLevel?: LogLevel): void; ensureRefId(): void; catch(err: Error): Error; finish(): void; - child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator: FilterCreator): ILogItem; + child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; serialize(filter: LogFilter, parentStartTime: number | null, forced: boolean): ISerializedItem | null; } @@ -94,18 +93,18 @@ export class LogItem implements ILogItem { } /** start a new root log item and run it detached mode, see BaseLogger.runDetached */ - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator): ILogItem { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator); } /** start a new detached root log item and log a reference to it from this item */ - wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull, filterCreator: FilterCreator): void { + wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void { this.refDetached(this.runDetached(labelOrValues, callback, logLevel, filterCreator)); } /** logs a reference to a different log item, usually obtained from runDetached. This is useful if the referenced operation can't be awaited. */ - refDetached(logItem: ILogItem, logLevel: LogLevelOrNull = null): void { + refDetached(logItem: ILogItem, logLevel?: LogLevel): void { logItem.ensureRefId(); this.log({ref: (logItem as LogItem)._values.refId}, logLevel); } @@ -119,7 +118,7 @@ export class LogItem implements ILogItem { /** * Creates a new child item and runs it in `callback`. */ - wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel: LogLevelOrNull = null, filterCreator?: FilterCreator): unknown { + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): unknown { const item = this.child(labelOrValues, logLevel, filterCreator); return item.run(callback); } @@ -160,7 +159,7 @@ export class LogItem implements ILogItem { * * Hence, the child item is not returned. */ - log(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull = null): void { + log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void { const item = this.child(labelOrValues, logLevel); item.end = item.start; } @@ -291,7 +290,7 @@ export class LogItem implements ILogItem { return err; } - child(labelOrValues: LabelOrValues, logLevel: LogLevelOrNull, filterCreator?: FilterCreator): ILogItem { + child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { if (this.end !== null) { console.trace("log item is finished, additional logs will likely not be recorded"); } diff --git a/src/matrix/storage/idb/StorageFactory.ts b/src/matrix/storage/idb/StorageFactory.ts index 5c257bfb..120f73f5 100644 --- a/src/matrix/storage/idb/StorageFactory.ts +++ b/src/matrix/storage/idb/StorageFactory.ts @@ -101,8 +101,7 @@ async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: nu async (log) => { for (let i = startIdx; i < version; ++i) { const migrationFunc = schema[i]; - await log.wrap(`v${i + 1}`, (log) => migrationFunc(db, txn, localStorage, log), null); + await log.wrap(`v${i + 1}`, (log) => migrationFunc(db, txn, localStorage, log)); } - }, - null) as Promise; + }) as Promise; } diff --git a/src/matrix/storage/idb/Transaction.ts b/src/matrix/storage/idb/Transaction.ts index c68a1759..4c3d2b5c 100644 --- a/src/matrix/storage/idb/Transaction.ts +++ b/src/matrix/storage/idb/Transaction.ts @@ -226,7 +226,7 @@ export class Transaction { }; const label = `${this._writeErrors.length} storage write operation(s) failed`; if (parentItem) { - parentItem.wrap(label, callback, null); + parentItem.wrap(label, callback); } else { this.logger.run(label, callback); } diff --git a/src/matrix/storage/idb/schema.ts b/src/matrix/storage/idb/schema.ts index b21bd30f..6250980d 100644 --- a/src/matrix/storage/idb/schema.ts +++ b/src/matrix/storage/idb/schema.ts @@ -196,7 +196,7 @@ async function fixMissingRoomsInUserIdentities(db: IDBDatabase, txn: IDBTransact const updatedIdentity = addRoomToIdentity(identity, userId, roomId); if (updatedIdentity) { log.log({l: `fixing up`, id: userId, - roomsBefore: originalRoomCount, roomsAfter: updatedIdentity.roomIds.length}, null); + roomsBefore: originalRoomCount, roomsAfter: updatedIdentity.roomIds.length}); userIdentitiesStore.put(updatedIdentity); 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 outboundGroupSessionsStore.delete(roomId); } - }, null); + }); } } From ba5f2032bafbb32a2700f5c8ee664be5b7ef348c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 15 Nov 2021 19:17:49 +0530 Subject: [PATCH 062/174] Make properties in LogItem optional, not null --- src/logging/LogItem.ts | 29 +++++++++++++---------------- src/logging/NullLogger.ts | 4 ++-- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index c0325ca7..012abc57 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -36,12 +36,12 @@ export interface ILogItem { logger: any; level: typeof LogLevel; duration?: number; - end?: number | null; + end?: number; start?: number; logLevel: LogLevel; - children: Array | null; + children?: Array; values: LogItemValues; - error: Error | null; + error?: Error; wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): unknown; log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void; set(key: string | object, value: unknown): void; @@ -73,22 +73,19 @@ export type LogCallback = (item: ILogItem) => unknown; export class LogItem implements ILogItem { public readonly start: number; public logLevel: LogLevel; - public error: Error | null; - public end: number | null; + public error?: Error; + public end?: number; private _values: LogItemValues; private _logger: BaseLogger; private _filterCreator?: FilterCreator; - private _children: Array | null; + private _children?: Array; constructor(labelOrValues: LabelOrValues, logLevel: LogLevel, logger: BaseLogger, filterCreator?: FilterCreator) { this._logger = logger; this.start = logger._now(); - this.end = null; // (l)abel this._values = typeof labelOrValues === "string" ? {l: labelOrValues} : labelOrValues; - this.error = null; this.logLevel = logLevel; - this._children = null; this._filterCreator = filterCreator; } @@ -183,7 +180,7 @@ export class LogItem implements ILogItem { } } let children: Array | null = null; - if (this._children !== null) { + if (this._children) { children = this._children.reduce((array: Array, c) => { const s = c.serialize(filter, this.start, false); if (s) { @@ -241,7 +238,7 @@ export class LogItem implements ILogItem { * @return {[type]} [description] */ run(callback: LogCallback): unknown { - if (this.end !== null) { + if (this.end) { console.trace("log item is finished, additional logs will likely not be recorded"); } let result: unknown; @@ -268,8 +265,8 @@ export class LogItem implements ILogItem { * @internal shouldn't typically be called by hand. allows to force finish if a promise is still running when closing the app */ finish(): void { - if (this.end === null) { - if (this._children !== null) { + if (!this.end) { + if (this._children) { for(const c of this._children) { c.finish(); } @@ -291,14 +288,14 @@ export class LogItem implements ILogItem { } child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { - if (this.end !== null) { + if (this.end) { console.trace("log item is finished, additional logs will likely not be recorded"); } if (!logLevel) { logLevel = this.logLevel || LogLevel.Info; } const item = new LogItem(labelOrValues, logLevel, this._logger, filterCreator); - if (this._children === null) { + if (!this._children) { this._children = []; } this._children.push(item); @@ -313,7 +310,7 @@ export class LogItem implements ILogItem { return this._values; } - get children(): Array | null { + get children(): Array | undefined { return this._children; } } diff --git a/src/logging/NullLogger.ts b/src/logging/NullLogger.ts index 285ea1e4..601a1c39 100644 --- a/src/logging/NullLogger.ts +++ b/src/logging/NullLogger.ts @@ -52,9 +52,9 @@ export class NullLogger { export class NullLogItem implements ILogItem { public readonly logger: NullLogger; public readonly logLevel: LogLevel; - public children: Array | null = null; + public children?: Array; public values: LogItemValues; - public error: Error | null = null; + public error?: Error; constructor(logger: NullLogger) { this.logger = logger; From fe69f84c8537247d158661cdf1f5ec77e5857e79 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 15 Nov 2021 19:32:16 +0530 Subject: [PATCH 063/174] Use undefined in LogItem.serialize --- src/logging/IDBLogger.ts | 2 +- src/logging/LogItem.ts | 8 ++++---- src/logging/NullLogger.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index d5ce2f9a..dcfd3a64 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -118,7 +118,7 @@ export class IDBLogger extends BaseLogger { } _persistItem(logItem: ILogItem, filter: LogFilter, forced: boolean): void { - const serializedItem = logItem.serialize(filter, null, forced); + const serializedItem = logItem.serialize(filter, undefined, forced); this._queuedItems.push({ json: JSON.stringify(serializedItem) }); diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 012abc57..82f785e4 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -53,7 +53,7 @@ export interface ILogItem { catch(err: Error): Error; finish(): void; child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; - serialize(filter: LogFilter, parentStartTime: number | null, forced: boolean): ISerializedItem | null; + serialize(filter: LogFilter, parentStartTime: number | undefined, forced: boolean): ISerializedItem | undefined; } export type LogItemValues = { @@ -171,7 +171,7 @@ export class LogItem implements ILogItem { } // todo: null or undefined here? - serialize(filter: LogFilter, parentStartTime: number | null = null, forced: boolean): ISerializedItem | null { + serialize(filter: LogFilter, parentStartTime: number | undefined, forced: boolean): ISerializedItem | undefined { if (this._filterCreator) { try { filter = this._filterCreator(new LogFilter(filter), this); @@ -193,12 +193,12 @@ export class LogItem implements ILogItem { }, null); } if (filter && !filter.filter(this, children)) { - return null; + return; } // in (v)alues, (l)abel and (t)ype are also reserved. const item: ISerializedItem = { // (s)tart - s: parentStartTime === null ? this.start : this.start - parentStartTime, + s: parentStartTime? this.start - parentStartTime : this.start, // (d)uration d: this.duration, // (v)alues diff --git a/src/logging/NullLogger.ts b/src/logging/NullLogger.ts index 601a1c39..63e91f61 100644 --- a/src/logging/NullLogger.ts +++ b/src/logging/NullLogger.ts @@ -102,7 +102,7 @@ export class NullLogItem implements ILogItem { finish(): void {} serialize() { - return null; + return undefined; } } From 4c1d7a8f2d098a0fbfa850fd1f421e52f4d080a6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 15 Nov 2021 22:47:38 +0530 Subject: [PATCH 064/174] Use generics over returning unknown --- src/logging/BaseLogger.ts | 9 +++++---- src/logging/LogItem.ts | 20 ++++++++++---------- src/logging/NullLogger.ts | 8 ++++---- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 6c377d48..a2463d3a 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -21,6 +21,7 @@ import type {FilterCreator, LabelOrValues, LogCallback, ILogItem} from "./LogIte // 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"; +type RunResult = T | void | Promise; export abstract class BaseLogger { protected _openItems: Set = new Set(); @@ -37,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. */ - wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): unknown { + wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): RunResult { if (item) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { @@ -50,7 +51,7 @@ export abstract class BaseLogger { Useful to pair with LogItem.refDetached. @return {LogItem} the log item added, useful to pass to LogItem.refDetached */ - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { // todo: Remove jsdoc type? if (!logLevel) { logLevel = LogLevel.Info; @@ -63,7 +64,7 @@ export abstract class BaseLogger { /** run a callback wrapped in a log operation. Errors and duration are transparently logged, also for async operations. Whatever the callback returns is returned here. */ - run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): unknown { + run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): RunResult { if (!logLevel) { logLevel = LogLevel.Info; } @@ -71,7 +72,7 @@ export abstract class BaseLogger { return this._run(item, callback, logLevel!, true, filterCreator); } - _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, shouldThrow: boolean, filterCreator?: FilterCreator): unknown { + _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, shouldThrow: boolean, filterCreator?: FilterCreator): RunResult { this._openItems.add(item); const finishItem = () => { diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 82f785e4..4600b6d5 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -42,12 +42,12 @@ export interface ILogItem { children?: Array; values: LogItemValues; error?: Error; - wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): unknown; + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T | Promise; log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void; set(key: string | object, value: unknown): void; - run(callback: LogCallback): unknown; - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; - wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void; + run(callback: LogCallback): T | Promise; + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; + wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void; refDetached(logItem: ILogItem, logLevel?: LogLevel): void; ensureRefId(): void; catch(err: Error): Error; @@ -68,7 +68,7 @@ export type LogItemValues = { export type LabelOrValues = string | LogItemValues; export type FilterCreator = ((filter: LogFilter, item: ILogItem) => LogFilter); -export type LogCallback = (item: ILogItem) => unknown; +export type LogCallback = (item: ILogItem) => T; export class LogItem implements ILogItem { public readonly start: number; @@ -90,12 +90,12 @@ export class LogItem implements ILogItem { } /** start a new root log item and run it detached mode, see BaseLogger.runDetached */ - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator); } /** start a new detached root log item and log a reference to it from this item */ - wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void { + wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void { this.refDetached(this.runDetached(labelOrValues, callback, logLevel, filterCreator)); } @@ -115,7 +115,7 @@ export class LogItem implements ILogItem { /** * Creates a new child item and runs it in `callback`. */ - wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): unknown { + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T | Promise { const item = this.child(labelOrValues, logLevel, filterCreator); return item.run(callback); } @@ -237,11 +237,11 @@ export class LogItem implements ILogItem { * @param {Function} callback [description] * @return {[type]} [description] */ - run(callback: LogCallback): unknown { + run(callback: LogCallback): T | Promise { if (this.end) { console.trace("log item is finished, additional logs will likely not be recorded"); } - let result: unknown; + let result: T | Promise; try { result = callback(this); if (result instanceof Promise) { diff --git a/src/logging/NullLogger.ts b/src/logging/NullLogger.ts index 63e91f61..9db1eee4 100644 --- a/src/logging/NullLogger.ts +++ b/src/logging/NullLogger.ts @@ -60,22 +60,22 @@ export class NullLogItem implements ILogItem { this.logger = logger; } - wrap(_: LabelOrValues, callback: LogCallback): unknown { + wrap(_: LabelOrValues, callback: LogCallback): T | Promise { return callback(this); } log(): void {} set(): void {} - runDetached(_: LabelOrValues, callback: LogCallback): ILogItem { + runDetached(_: LabelOrValues, callback: LogCallback): ILogItem { new Promise(r => r(callback(this))).then(noop, noop); return this; } - wrapDetached(_: LabelOrValues, _callback: LogCallback): void { + wrapDetached(_: LabelOrValues, _callback: LogCallback): void { return this.refDetached(); } - run(callback: LogCallback): unknown { + run(callback: LogCallback): T | Promise { return callback(this); } From 4161d31642bc664ebaec9b3e51780635a5e88587 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 12:23:06 +0530 Subject: [PATCH 065/174] Convert NullLogger to typescript --- src/logging/NullLogger.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/logging/NullLogger.ts b/src/logging/NullLogger.ts index 9db1eee4..d1ac2a03 100644 --- a/src/logging/NullLogger.ts +++ b/src/logging/NullLogger.ts @@ -18,21 +18,20 @@ import type {ILogItem, LabelOrValues, LogCallback, LogItemValues} from "./LogIte function noop (): void {} - export class NullLogger { public readonly item: ILogItem = new NullLogItem(this); log(): void {} - run(_: null, callback) { + run(_, callback: LogCallback): T | Promise { return callback(this.item); } - wrapOrRun(item, _, callback) { + wrapOrRun(item: ILogItem, _, callback: LogCallback): T | Promise { if (item) { - return item.wrap(null, callback); + return item.wrap(_, callback); } else { - return this.run(null, callback); + return this.run(_, callback); } } @@ -40,11 +39,11 @@ export class NullLogger { new Promise(r => r(callback(this.item))).then(noop, noop); } - async export() { + async export(): Promise { return null; } - get level() { + get level(): typeof LogLevel { return LogLevel; } } @@ -63,6 +62,7 @@ export class NullLogItem implements ILogItem { wrap(_: LabelOrValues, callback: LogCallback): T | Promise { return callback(this); } + log(): void {} set(): void {} @@ -95,13 +95,13 @@ export class NullLogItem implements ILogItem { return err; } - child() { + child(): ILogItem { return this; } finish(): void {} - serialize() { + serialize(): undefined { return undefined; } } From 3ee160729883dd6ac71f1d1e05da112f18f54781 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 12:32:49 +0530 Subject: [PATCH 066/174] Convert utils to typescript --- src/logging/utils.js | 16 ---------------- src/logging/utils.ts | 18 ++++++++++++++++++ src/matrix/room/BaseRoom.js | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) delete mode 100644 src/logging/utils.js create mode 100644 src/logging/utils.ts diff --git a/src/logging/utils.js b/src/logging/utils.js deleted file mode 100644 index 659df055..00000000 --- a/src/logging/utils.js +++ /dev/null @@ -1,16 +0,0 @@ -// 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. - -import {Instance as NullLoggerInstance} from "./NullLogger"; - -export function wrapOrRunNullLogger(logItem, labelOrValues, callback, logLevel = null, filterCreator = null) { - if (logItem) { - return logItem.wrap(logItem, labelOrValues, callback, logLevel, filterCreator); - } else { - return NullLoggerInstance.run(null, callback); - } -} - -export function ensureLogItem(logItem) { - return logItem || NullLoggerInstance.item; -} diff --git a/src/logging/utils.ts b/src/logging/utils.ts new file mode 100644 index 00000000..61cbce80 --- /dev/null +++ b/src/logging/utils.ts @@ -0,0 +1,18 @@ +// 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. + +import {Instance as NullLoggerInstance} from "./NullLogger"; +import type {FilterCreator, ILogItem, LabelOrValues, LogCallback} from "./LogItem"; +import {LogLevel} from "./LogFilter"; + +export function wrapOrRunNullLogger(logItem: ILogItem | undefined, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T | Promise { + if (logItem) { + return logItem.wrap(labelOrValues, callback, logLevel, filterCreator); + } else { + return NullLoggerInstance.run(null, callback); + } +} + +export function ensureLogItem(logItem: ILogItem): ILogItem { + return logItem || NullLoggerInstance.item; +} diff --git a/src/matrix/room/BaseRoom.js b/src/matrix/room/BaseRoom.js index 1aa8cb18..93973b71 100644 --- a/src/matrix/room/BaseRoom.js +++ b/src/matrix/room/BaseRoom.js @@ -27,7 +27,7 @@ import {Heroes} from "./members/Heroes.js"; import {EventEntry} from "./timeline/entries/EventEntry.js"; import {ObservedEventMap} from "./ObservedEventMap.js"; import {DecryptionSource} from "../e2ee/common.js"; -import {ensureLogItem} from "../../logging/utils.js"; +import {ensureLogItem} from "../../logging/utils"; import {PowerLevels} from "./PowerLevels.js"; import {RetainedObservableValue} from "../../observable/ObservableValue"; From 286747c23c01074b28ddf29f5e281addb1c31634 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 12:41:03 +0530 Subject: [PATCH 067/174] Add type annotation for ctor --- src/logging/IDBLogger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index dcfd3a64..c5fa2a52 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -41,7 +41,7 @@ export class IDBLogger extends BaseLogger { private _queuedItems: QueuedItem[]; // todo: type constructor - constructor(options) { + constructor(options: {name: string, flushInterval?: number, limit?: number, platform: Platform}) { super(options); const {name, flushInterval = 60 * 1000, limit = 3000} = options; this._name = name; From e339e730f413d30d3a2891dd5074783c8e817b34 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 12:42:50 +0530 Subject: [PATCH 068/174] Remove todo comment --- src/logging/IDBLogger.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index c5fa2a52..944116b8 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -40,7 +40,6 @@ export class IDBLogger extends BaseLogger { private readonly _flushInterval: Interval; private _queuedItems: QueuedItem[]; - // todo: type constructor constructor(options: {name: string, flushInterval?: number, limit?: number, platform: Platform}) { super(options); const {name, flushInterval = 60 * 1000, limit = 3000} = options; From 34a8463bf912af38a0708586de6bbcb107af8930 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 12:43:23 +0530 Subject: [PATCH 069/174] Fix jsdoc return type --- src/logging/BaseLogger.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index a2463d3a..f6c88aff 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -50,9 +50,8 @@ export abstract class BaseLogger { where the (async) result or errors are not propagated but still logged. Useful to pair with LogItem.refDetached. - @return {LogItem} the log item added, useful to pass to LogItem.refDetached */ + @return {ILogItem} the log item added, useful to pass to LogItem.refDetached */ runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { - // todo: Remove jsdoc type? if (!logLevel) { logLevel = LogLevel.Info; } From 4704a70cb78d5c2c35e57c54248958ca9ad36956 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 13:06:47 +0530 Subject: [PATCH 070/174] Remove todo comment --- src/logging/BaseLogger.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index f6c88aff..ddf1b308 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -18,7 +18,6 @@ limitations under the License. import {LogItem} from "./LogItem"; import {LogLevel, LogFilter} from "./LogFilter"; import type {FilterCreator, LabelOrValues, LogCallback, ILogItem} from "./LogItem"; -// 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"; type RunResult = T | void | Promise; From 58105824d9669ed2fa9dafb9b76ee2c84965c311 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 13:08:13 +0530 Subject: [PATCH 071/174] Fix error in reduce --- src/logging/LogItem.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 4600b6d5..c12308ef 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -141,9 +141,7 @@ export class LogItem implements ILogItem { } else if (this._children) { return this._children.reduce((sum, c) => { const duration = c.durationOfType(type); - if (duration) { - return sum + duration; - } + return sum + (duration ?? 0); }, 0); } else { return 0; From 14eaa574342eca3cf6443fbdb0d9ed019bdeee8b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 13:14:11 +0530 Subject: [PATCH 072/174] No need for type assertion here --- src/matrix/storage/idb/StorageFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/storage/idb/StorageFactory.ts b/src/matrix/storage/idb/StorageFactory.ts index 120f73f5..3c7e0e40 100644 --- a/src/matrix/storage/idb/StorageFactory.ts +++ b/src/matrix/storage/idb/StorageFactory.ts @@ -103,5 +103,5 @@ async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: nu const migrationFunc = schema[i]; await log.wrap(`v${i + 1}`, (log) => migrationFunc(db, txn, localStorage, log)); } - }) as Promise; + }); } From 8fce29caf7593c82677e35342e41d9e88d8a046b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 11:38:33 +0530 Subject: [PATCH 073/174] Explicitly check for undefined --- src/logging/LogFilter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index 9968e63e..5f0e7ae7 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -42,7 +42,7 @@ export class LogFilter { } } // neither our children or us have a loglevel high enough, filter out. - if (this._min && !Array.isArray(children) && item.logLevel < this._min) { + if (this._min !== undefined && !Array.isArray(children) && item.logLevel < this._min) { return false; } else { return true; From b0ab8cd77f8ae373b07cb330e1f7881c17192253 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 11:40:38 +0530 Subject: [PATCH 074/174] Space before { --- src/logging/LogItem.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index c12308ef..717eadfd 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -120,7 +120,7 @@ export class LogItem implements ILogItem { return item.run(callback); } - get duration(): number | undefined{ + get duration(): number | undefined { if (this.end) { return this.end - this.start; } else { @@ -128,7 +128,7 @@ export class LogItem implements ILogItem { } } - durationWithoutType(type: string): number | undefined{ + durationWithoutType(type: string): number | undefined { const durationOfType = this.durationOfType(type); if (this.duration && durationOfType) { return this.duration - durationOfType; From 9c8f96e233e2bb16234d5a536d39d88932f0a9f0 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Wed, 17 Nov 2021 11:43:59 +0530 Subject: [PATCH 075/174] value is optional Co-authored-by: Bruno Windels --- src/logging/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 717eadfd..fd9fcb1d 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -159,7 +159,7 @@ export class LogItem implements ILogItem { item.end = item.start; } - set(key: string | object, value: unknown): void { + set(key: string | object, value?: unknown): void { if(typeof key === "object") { const values = key; Object.assign(this._values, values); From 835da58b53fe745de68741a548fd45d7695d6c15 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 11:59:50 +0530 Subject: [PATCH 076/174] Remove ! --- src/logging/BaseLogger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index ddf1b308..c9ce6aaf 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -55,7 +55,7 @@ export abstract class BaseLogger { logLevel = LogLevel.Info; } const item = new LogItem(labelOrValues, logLevel, this); - this._run(item, callback, logLevel!, false /* don't throw, nobody is awaiting */, filterCreator); + this._run(item, callback, logLevel, false /* don't throw, nobody is awaiting */, filterCreator); return item; } From 07a1130db33963ba4090657432aed766fd8af501 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 12:02:12 +0530 Subject: [PATCH 077/174] children can be array of ISerializedItem --- src/logging/LogFilter.ts | 4 ++-- src/logging/LogItem.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index 5f0e7ae7..d0189631 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import type {ILogItem} from "./LogItem"; +import type {ILogItem, ISerializedItem} from "./LogItem"; export enum LogLevel { All = 1, @@ -35,7 +35,7 @@ export class LogFilter { this._parentFilter = parentFilter; } - filter(item: ILogItem, children: Array | null): boolean { + filter(item: ILogItem, children: ISerializedItem[] | null): boolean { if (this._parentFilter) { if (!this._parentFilter.filter(item, children)) { return false; diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index fd9fcb1d..30a6294a 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -18,7 +18,7 @@ limitations under the License. import {LogLevel, LogFilter} from "./LogFilter"; import type {BaseLogger} from "./BaseLogger"; -interface ISerializedItem { +export interface ISerializedItem { s: number; d?: number; v: LogItemValues; From d01271fb150ba19f0183c25e3cd3ffaa78d8899e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 13:22:19 +0530 Subject: [PATCH 078/174] _run return T or void depending on boolean --- src/logging/BaseLogger.ts | 26 +++++++++++++++----------- src/logging/LogItem.ts | 10 +++++----- src/logging/NullLogger.ts | 4 ++-- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index c9ce6aaf..999db4cf 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -20,8 +20,6 @@ import {LogLevel, LogFilter} from "./LogFilter"; import type {FilterCreator, LabelOrValues, LogCallback, ILogItem} from "./LogItem"; import type {Platform} from "../platform/web/Platform.js"; -type RunResult = T | void | Promise; - export abstract class BaseLogger { protected _openItems: Set = new Set(); protected _platform: Platform; @@ -37,7 +35,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. */ - wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): RunResult { + wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T { if (item) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { @@ -62,7 +60,7 @@ export abstract class BaseLogger { /** run a callback wrapped in a log operation. Errors and duration are transparently logged, also for async operations. Whatever the callback returns is returned here. */ - run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): RunResult { + run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T { if (!logLevel) { logLevel = LogLevel.Info; } @@ -70,7 +68,9 @@ export abstract class BaseLogger { return this._run(item, callback, logLevel!, true, filterCreator); } - _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, shouldThrow: boolean, filterCreator?: FilterCreator): RunResult { + _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: true, filterCreator?: FilterCreator): T; + _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: false, filterCreator?: FilterCreator): void; + _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: boolean, filterCreator?: FilterCreator): T | void { this._openItems.add(item); const finishItem = () => { @@ -94,24 +94,28 @@ export abstract class BaseLogger { }; try { - const result = item.run(callback); + let result = item.run(callback); if (result instanceof Promise) { - return result.then(promiseResult => { + result = result.then(promiseResult => { finishItem(); return promiseResult; }, err => { finishItem(); - if (shouldThrow) { + if (wantResult) { throw err; } - }); - } else { + }) as unknown as T; + if (wantResult) { + return result; + } + } + if(wantResult) { finishItem(); return result; } } catch (err) { finishItem(); - if (shouldThrow) { + if (wantResult) { throw err; } } diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 30a6294a..c3d32ef5 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -42,10 +42,10 @@ export interface ILogItem { children?: Array; values: LogItemValues; error?: Error; - wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T | Promise; + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T; log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void; set(key: string | object, value: unknown): void; - run(callback: LogCallback): T | Promise; + run(callback: LogCallback): T; runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void; refDetached(logItem: ILogItem, logLevel?: LogLevel): void; @@ -115,7 +115,7 @@ export class LogItem implements ILogItem { /** * Creates a new child item and runs it in `callback`. */ - wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T | Promise { + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T { const item = this.child(labelOrValues, logLevel, filterCreator); return item.run(callback); } @@ -235,7 +235,7 @@ export class LogItem implements ILogItem { * @param {Function} callback [description] * @return {[type]} [description] */ - run(callback: LogCallback): T | Promise { + run(callback: LogCallback): T { if (this.end) { console.trace("log item is finished, additional logs will likely not be recorded"); } @@ -248,7 +248,7 @@ export class LogItem implements ILogItem { return promiseResult; }, err => { throw this.catch(err); - }); + }) as unknown as T; } else { this.finish(); return result; diff --git a/src/logging/NullLogger.ts b/src/logging/NullLogger.ts index d1ac2a03..eba453e6 100644 --- a/src/logging/NullLogger.ts +++ b/src/logging/NullLogger.ts @@ -59,7 +59,7 @@ export class NullLogItem implements ILogItem { this.logger = logger; } - wrap(_: LabelOrValues, callback: LogCallback): T | Promise { + wrap(_: LabelOrValues, callback: LogCallback): T { return callback(this); } @@ -75,7 +75,7 @@ export class NullLogItem implements ILogItem { return this.refDetached(); } - run(callback: LogCallback): T | Promise { + run(callback: LogCallback): T { return callback(this); } From 1942c31eff00b46451efc27d410ccf009548968d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 10:42:54 +0100 Subject: [PATCH 079/174] still finish item when not returning from sync callback --- src/logging/BaseLogger.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 999db4cf..f14caa5e 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -69,6 +69,7 @@ export abstract class BaseLogger { } _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: true, filterCreator?: FilterCreator): T; + // we don't return if we don't throw, as we don't have anything to return when an error is caught but swallowed for the fire-and-forget case. _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: false, filterCreator?: FilterCreator): void; _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: boolean, filterCreator?: FilterCreator): T | void { this._openItems.add(item); @@ -108,10 +109,11 @@ export abstract class BaseLogger { if (wantResult) { return result; } - } - if(wantResult) { + } else { finishItem(); - return result; + if(wantResult) { + return result; + } } } catch (err) { finishItem(); From f93bdd962a8085d827cfc14bc5c3757f9d817b47 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 10:50:55 +0100 Subject: [PATCH 080/174] might as well use generic here --- src/logging/BaseLogger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index f14caa5e..4b04b510 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -48,7 +48,7 @@ export abstract class BaseLogger { Useful to pair with LogItem.refDetached. @return {ILogItem} the log item added, useful to pass to LogItem.refDetached */ - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { if (!logLevel) { logLevel = LogLevel.Info; } From 90d7b73dd4b6e328667f7e89132f9352d2d5b547 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 11:08:29 +0100 Subject: [PATCH 081/174] non-persisted queued items don't have an id yet, find them by ref equality --- src/logging/IDBLogger.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 944116b8..ebcf2249 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -152,12 +152,14 @@ export class IDBLogger extends BaseLogger { const txn = db.transaction(["logs"], "readwrite"); const logs = txn.objectStore("logs"); for (const item of items) { - const queuedIdx = this._queuedItems.findIndex(i => i.id === item.id); - if (queuedIdx === -1) { - // todo: isn't id optional? do we need further checks here - logs.delete(item.id!); // resolve questionable use of non-null assertion operator? + if (typeof item.id === "number") { + logs.delete(item.id); } else { - this._queuedItems.splice(queuedIdx, 1); + // assume the (non-persisted) object in each array will be the same + const queuedIdx = this._queuedItems.indexOf(item); + if (queuedIdx === -1) { + this._queuedItems.splice(queuedIdx, 1); + } } } await txnAsPromise(txn); From 3ee7e73ff0530f33c4a3d1d95d287c831ba81cd9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 11:08:44 +0100 Subject: [PATCH 082/174] item is optional here --- src/logging/BaseLogger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 4b04b510..3b2842ad 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -35,7 +35,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. */ - wrapOrRun(item: ILogItem, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T { + wrapOrRun(item: ILogItem | undefined, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T { if (item) { return item.wrap(labelOrValues, callback, logLevel, filterCreator); } else { From 1b13f32d94a17a198533fc001f331078388df7a0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 15:22:08 +0530 Subject: [PATCH 083/174] Remove resolved todo comment --- src/logging/LogItem.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index c3d32ef5..9521b67b 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -168,7 +168,6 @@ export class LogItem implements ILogItem { } } - // todo: null or undefined here? serialize(filter: LogFilter, parentStartTime: number | undefined, forced: boolean): ISerializedItem | undefined { if (this._filterCreator) { try { From 695996d6e2f5f66edc617aa4b9a0ef54bde630a7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 11:38:17 +0100 Subject: [PATCH 084/174] add ILogger and ILogExport interface, to give export correct return type also move logging related types to own file --- src/logging/BaseLogger.ts | 6 +- src/logging/ConsoleLogger.ts | 6 +- src/logging/IDBLogger.ts | 6 +- src/logging/LogFilter.ts | 2 +- src/logging/LogItem.ts | 53 +---------- src/logging/NullLogger.ts | 15 ++-- src/logging/types.ts | 87 +++++++++++++++++++ src/logging/utils.ts | 2 +- src/matrix/e2ee/megolm/Decryption.ts | 2 +- src/matrix/storage/idb/QueryTarget.ts | 2 +- src/matrix/storage/idb/Storage.ts | 6 +- src/matrix/storage/idb/StorageFactory.ts | 3 +- src/matrix/storage/idb/Store.ts | 2 +- src/matrix/storage/idb/Transaction.ts | 5 +- src/matrix/storage/idb/schema.ts | 2 +- src/matrix/storage/idb/stores/SessionStore.ts | 2 +- .../storage/idb/stores/TimelineEventStore.ts | 2 +- 17 files changed, 119 insertions(+), 84 deletions(-) create mode 100644 src/logging/types.ts diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 3b2842ad..7d015262 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -17,10 +17,10 @@ limitations under the License. import {LogItem} from "./LogItem"; import {LogLevel, LogFilter} from "./LogFilter"; -import type {FilterCreator, LabelOrValues, LogCallback, ILogItem} from "./LogItem"; +import type {ILogger, ILogExport, FilterCreator, LabelOrValues, LogCallback, ILogItem} from "./types"; import type {Platform} from "../platform/web/Platform.js"; -export abstract class BaseLogger { +export abstract class BaseLogger implements ILogger { protected _openItems: Set = new Set(); protected _platform: Platform; @@ -141,7 +141,7 @@ export abstract class BaseLogger { abstract _persistItem(item: ILogItem, filter?: LogFilter, forced?: boolean): void; - abstract export(): void; + abstract export(): Promise; // expose log level without needing get level(): typeof LogLevel { diff --git a/src/logging/ConsoleLogger.ts b/src/logging/ConsoleLogger.ts index d8693e38..20fdf6a6 100644 --- a/src/logging/ConsoleLogger.ts +++ b/src/logging/ConsoleLogger.ts @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ import {BaseLogger} from "./BaseLogger"; -import type {ILogItem, LogItemValues} from "./LogItem"; +import type {ILogItem, LogItemValues, ILogExport} from "./types"; export class ConsoleLogger extends BaseLogger { _persistItem(item: ILogItem): void { printToConsole(item); } - export(): void { - throw new Error("Cannot export from ConsoleLogger"); + async export(): Promise { + return undefined; } } diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index ebcf2249..621359dc 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -26,7 +26,7 @@ import {BaseLogger} from "./BaseLogger"; import type {Interval} from "../platform/web/dom/Clock"; import type {Platform} from "../platform/web/Platform.js"; import type {BlobHandle} from "../platform/web/dom/BlobHandle.js"; -import type {ILogItem} from "./LogItem"; +import type {ILogItem, ILogExport} from "./types"; import type {LogFilter} from "./LogFilter"; type QueuedItem = { @@ -131,7 +131,7 @@ export class IDBLogger extends BaseLogger { } } - async export(): Promise { + async export(): Promise { const db = await this._openDB(); try { const txn = db.transaction(["logs"], "readonly"); @@ -171,7 +171,7 @@ export class IDBLogger extends BaseLogger { } } -class IDBLogExport { +class IDBLogExport implements ILogExport { private readonly _items: QueuedItem[]; private readonly _logger: IDBLogger; private readonly _platform: Platform; diff --git a/src/logging/LogFilter.ts b/src/logging/LogFilter.ts index d0189631..8611d5f3 100644 --- a/src/logging/LogFilter.ts +++ b/src/logging/LogFilter.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import type {ILogItem, ISerializedItem} from "./LogItem"; +import type {ILogItem, ISerializedItem} from "./types"; export enum LogLevel { All = 1, diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 9521b67b..7774dd41 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -17,58 +17,7 @@ limitations under the License. import {LogLevel, LogFilter} from "./LogFilter"; import type {BaseLogger} from "./BaseLogger"; - -export interface ISerializedItem { - s: number; - d?: number; - v: LogItemValues; - l: LogLevel; - e?: { - stack?: string; - name: string; - message: string; - }; - f?: boolean; - c?: Array; -}; - -export interface ILogItem { - logger: any; - level: typeof LogLevel; - duration?: number; - end?: number; - start?: number; - logLevel: LogLevel; - children?: Array; - values: LogItemValues; - error?: Error; - wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T; - log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void; - set(key: string | object, value: unknown): void; - run(callback: LogCallback): T; - runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; - wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void; - refDetached(logItem: ILogItem, logLevel?: LogLevel): void; - ensureRefId(): void; - catch(err: Error): Error; - finish(): void; - child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; - serialize(filter: LogFilter, parentStartTime: number | undefined, forced: boolean): ISerializedItem | undefined; -} - -export type LogItemValues = { - l?: string; - t?: string; - id?: unknown; - status?: string | number; - refId?: number; - ref?: number; - [key: string]: any -} - -export type LabelOrValues = string | LogItemValues; -export type FilterCreator = ((filter: LogFilter, item: ILogItem) => LogFilter); -export type LogCallback = (item: ILogItem) => T; +import type {ISerializedItem, ILogItem, LogItemValues, LabelOrValues, FilterCreator, LogCallback} from "./types"; export class LogItem implements ILogItem { public readonly start: number; diff --git a/src/logging/NullLogger.ts b/src/logging/NullLogger.ts index eba453e6..ed12cd78 100644 --- a/src/logging/NullLogger.ts +++ b/src/logging/NullLogger.ts @@ -14,20 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ import {LogLevel} from "./LogFilter"; -import type {ILogItem, LabelOrValues, LogCallback, LogItemValues} from "./LogItem"; +import type {ILogger, ILogExport, ILogItem, LabelOrValues, LogCallback, LogItemValues} from "./types"; function noop (): void {} -export class NullLogger { +export class NullLogger implements ILogger { public readonly item: ILogItem = new NullLogItem(this); log(): void {} - run(_, callback: LogCallback): T | Promise { + run(_, callback: LogCallback): T { return callback(this.item); } - wrapOrRun(item: ILogItem, _, callback: LogCallback): T | Promise { + wrapOrRun(item: ILogItem | undefined, _, callback: LogCallback): T { if (item) { return item.wrap(_, callback); } else { @@ -35,12 +35,13 @@ export class NullLogger { } } - runDetached(_, callback) { + runDetached(_, callback): ILogItem { new Promise(r => r(callback(this.item))).then(noop, noop); + return this.item; } - async export(): Promise { - return null; + async export(): Promise { + return undefined; } get level(): typeof LogLevel { diff --git a/src/logging/types.ts b/src/logging/types.ts new file mode 100644 index 00000000..6a29a7a2 --- /dev/null +++ b/src/logging/types.ts @@ -0,0 +1,87 @@ +/* +Copyright 2020 Bruno Windels +Copyright 2021 The Matrix.org Foundation C.I.C. + +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 {LogLevel, LogFilter} from "./LogFilter"; +import type {BaseLogger} from "./BaseLogger"; +import type {BlobHandle} from "../platform/web/dom/BlobHandle.js"; + +export interface ISerializedItem { + s: number; + d?: number; + v: LogItemValues; + l: LogLevel; + e?: { + stack?: string; + name: string; + message: string; + }; + f?: boolean; + c?: Array; +}; + +export interface ILogItem { + logger: any; + level: typeof LogLevel; + duration?: number; + end?: number; + start?: number; + logLevel: LogLevel; + children?: Array; + values: LogItemValues; + error?: Error; + wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T; + log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void; + set(key: string | object, value: unknown): void; + run(callback: LogCallback): T; + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; + wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void; + refDetached(logItem: ILogItem, logLevel?: LogLevel): void; + ensureRefId(): void; + catch(err: Error): Error; + finish(): void; + child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; + serialize(filter: LogFilter, parentStartTime: number | undefined, forced: boolean): ISerializedItem | undefined; +} + +export interface ILogger { + log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void; + wrapOrRun(item: ILogItem | undefined, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T; + runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; + run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T; + export(): Promise; + get level(): typeof LogLevel; +} + +export interface ILogExport { + get count(): number; + removeFromStore(): Promise; + asBlob(): BlobHandle; +} + +export type LogItemValues = { + l?: string; + t?: string; + id?: unknown; + status?: string | number; + refId?: number; + ref?: number; + [key: string]: any +} + +export type LabelOrValues = string | LogItemValues; +export type FilterCreator = ((filter: LogFilter, item: ILogItem) => LogFilter); +export type LogCallback = (item: ILogItem) => T; diff --git a/src/logging/utils.ts b/src/logging/utils.ts index 61cbce80..2b32454d 100644 --- a/src/logging/utils.ts +++ b/src/logging/utils.ts @@ -2,7 +2,7 @@ // 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"; -import type {FilterCreator, ILogItem, LabelOrValues, LogCallback} from "./LogItem"; +import type {FilterCreator, ILogItem, LabelOrValues, LogCallback} from "./types"; import {LogLevel} from "./LogFilter"; export function wrapOrRunNullLogger(logItem: ILogItem | undefined, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T | Promise { diff --git a/src/matrix/e2ee/megolm/Decryption.ts b/src/matrix/e2ee/megolm/Decryption.ts index ee96eca1..e139e8c9 100644 --- a/src/matrix/e2ee/megolm/Decryption.ts +++ b/src/matrix/e2ee/megolm/Decryption.ts @@ -26,7 +26,7 @@ import type {OlmWorker} from "../OlmWorker"; import type {Transaction} from "../../storage/idb/Transaction"; import type {TimelineEvent} from "../../storage/types"; import type {DecryptionResult} from "../DecryptionResult"; -import type {ILogItem} from "../../../logging/LogItem"; +import type {ILogItem} from "../../../logging/types"; export class Decryption { private keyLoader: KeyLoader; diff --git a/src/matrix/storage/idb/QueryTarget.ts b/src/matrix/storage/idb/QueryTarget.ts index 7519beac..5bea1139 100644 --- a/src/matrix/storage/idb/QueryTarget.ts +++ b/src/matrix/storage/idb/QueryTarget.ts @@ -16,7 +16,7 @@ limitations under the License. import {iterateCursor, DONE, NOT_DONE, reqAsPromise} from "./utils"; import {StorageError} from "../common"; -import {ILogItem} from "../../../logging/LogItem"; +import {ILogItem} from "../../../logging/types"; import {IDBKey} from "./Transaction"; // this is the part of the Transaction class API that is used here and in the Store subclass, diff --git a/src/matrix/storage/idb/Storage.ts b/src/matrix/storage/idb/Storage.ts index 728d4fca..6f3ed8ad 100644 --- a/src/matrix/storage/idb/Storage.ts +++ b/src/matrix/storage/idb/Storage.ts @@ -18,7 +18,7 @@ import {IDOMStorage} from "./types"; import {Transaction} from "./Transaction"; import { STORE_NAMES, StoreNames, StorageError } from "../common"; import { reqAsPromise } from "./utils"; -import { BaseLogger } from "../../../logging/BaseLogger"; +import { ILogger } from "../../../logging/types"; const WEBKITEARLYCLOSETXNBUG_BOGUS_KEY = "782rh281re38-boguskey"; @@ -26,13 +26,13 @@ export class Storage { private _db: IDBDatabase; private _hasWebkitEarlyCloseTxnBug: boolean; - readonly logger: BaseLogger; + readonly logger: ILogger; readonly idbFactory: IDBFactory readonly IDBKeyRange: typeof IDBKeyRange; readonly storeNames: typeof StoreNames; readonly localStorage: IDOMStorage; - constructor(idbDatabase: IDBDatabase, idbFactory: IDBFactory, _IDBKeyRange: typeof IDBKeyRange, hasWebkitEarlyCloseTxnBug: boolean, localStorage: IDOMStorage, logger: BaseLogger) { + constructor(idbDatabase: IDBDatabase, idbFactory: IDBFactory, _IDBKeyRange: typeof IDBKeyRange, hasWebkitEarlyCloseTxnBug: boolean, localStorage: IDOMStorage, logger: ILogger) { this._db = idbDatabase; this.idbFactory = idbFactory; this.IDBKeyRange = _IDBKeyRange; diff --git a/src/matrix/storage/idb/StorageFactory.ts b/src/matrix/storage/idb/StorageFactory.ts index 3c7e0e40..5cb1b6e5 100644 --- a/src/matrix/storage/idb/StorageFactory.ts +++ b/src/matrix/storage/idb/StorageFactory.ts @@ -20,8 +20,7 @@ import { openDatabase, reqAsPromise } from "./utils"; import { exportSession, importSession, Export } from "./export"; import { schema } from "./schema"; import { detectWebkitEarlyCloseTxnBug } from "./quirks"; -import { BaseLogger } from "../../../logging/BaseLogger"; -import { ILogItem } from "../../../logging/LogItem"; +import { ILogItem } from "../../../logging/types"; const sessionName = (sessionId: string) => `hydrogen_session_${sessionId}`; const openDatabaseWithSessionId = function(sessionId: string, idbFactory: IDBFactory, localStorage: IDOMStorage, log: ILogItem) { diff --git a/src/matrix/storage/idb/Store.ts b/src/matrix/storage/idb/Store.ts index 5a2a9abc..07cc90b0 100644 --- a/src/matrix/storage/idb/Store.ts +++ b/src/matrix/storage/idb/Store.ts @@ -18,7 +18,7 @@ import {QueryTarget, IDBQuery, ITransaction} from "./QueryTarget"; import {IDBRequestError, IDBRequestAttemptError} from "./error"; import {reqAsPromise} from "./utils"; import {Transaction, IDBKey} from "./Transaction"; -import {ILogItem} from "../../../logging/LogItem"; +import {ILogItem} from "../../../logging/types"; const LOG_REQUESTS = false; diff --git a/src/matrix/storage/idb/Transaction.ts b/src/matrix/storage/idb/Transaction.ts index 4c3d2b5c..80894105 100644 --- a/src/matrix/storage/idb/Transaction.ts +++ b/src/matrix/storage/idb/Transaction.ts @@ -36,8 +36,7 @@ import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore"; import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore"; import {OperationStore} from "./stores/OperationStore"; import {AccountDataStore} from "./stores/AccountDataStore"; -import {ILogItem} from "../../../logging/LogItem"; -import {BaseLogger} from "../../../logging/BaseLogger"; +import type {ILogger, ILogItem} from "../../../logging/types"; export type IDBKey = IDBValidKey | IDBKeyRange; @@ -77,7 +76,7 @@ export class Transaction { return this._storage.databaseName; } - get logger(): BaseLogger { + get logger(): ILogger { return this._storage.logger; } diff --git a/src/matrix/storage/idb/schema.ts b/src/matrix/storage/idb/schema.ts index 6250980d..ad3e5896 100644 --- a/src/matrix/storage/idb/schema.ts +++ b/src/matrix/storage/idb/schema.ts @@ -11,7 +11,7 @@ import {SessionStore} from "./stores/SessionStore"; import {Store} from "./Store"; import {encodeScopeTypeKey} from "./stores/OperationStore"; import {MAX_UNICODE} from "./stores/common"; -import {ILogItem} from "../../../logging/LogItem"; +import {ILogItem} from "../../../logging/types"; export type MigrationFunc = (db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem) => Promise | void; diff --git a/src/matrix/storage/idb/stores/SessionStore.ts b/src/matrix/storage/idb/stores/SessionStore.ts index dd133b45..7faedc41 100644 --- a/src/matrix/storage/idb/stores/SessionStore.ts +++ b/src/matrix/storage/idb/stores/SessionStore.ts @@ -16,8 +16,8 @@ limitations under the License. import {Store} from "../Store"; import {IDOMStorage} from "../types"; import {SESSION_E2EE_KEY_PREFIX} from "../../../e2ee/common.js"; -import {ILogItem} from "../../../../logging/LogItem"; import {parse, stringify} from "../../../../utils/typedJSON"; +import type {ILogItem} from "../../../../logging/types"; export interface SessionEntry { key: string; diff --git a/src/matrix/storage/idb/stores/TimelineEventStore.ts b/src/matrix/storage/idb/stores/TimelineEventStore.ts index b3663c29..bb6f652f 100644 --- a/src/matrix/storage/idb/stores/TimelineEventStore.ts +++ b/src/matrix/storage/idb/stores/TimelineEventStore.ts @@ -20,7 +20,7 @@ import { encodeUint32, decodeUint32 } from "../utils"; import {KeyLimits} from "../../common"; import {Store} from "../Store"; import {TimelineEvent, StateEvent} from "../../types"; -import {ILogItem} from "../../../../logging/LogItem"; +import {ILogItem} from "../../../../logging/types"; interface Annotation { count: number; From 5f362cbdbde6b17cfe28e6b39e38d8e8a2245b9a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 11:54:29 +0100 Subject: [PATCH 085/174] remove dead code --- src/logging/ConsoleLogger.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/logging/ConsoleLogger.ts b/src/logging/ConsoleLogger.ts index 20fdf6a6..f2a2efd1 100644 --- a/src/logging/ConsoleLogger.ts +++ b/src/logging/ConsoleLogger.ts @@ -28,10 +28,6 @@ export class ConsoleLogger extends BaseLogger { const excludedKeysFromTable = ["l", "id"]; function filterValues(values: LogItemValues): LogItemValues | null { - if (!values) { - // todo: is this check here unnecessary because LogItem will always have values? - return null; - } return Object.entries(values) .filter(([key]) => !excludedKeysFromTable.includes(key)) .reduce((obj: LogItemValues, [key, value]) => { From b1d20178f816addce705fa143f87a8d1009f013e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 12:37:50 +0100 Subject: [PATCH 086/174] add explicit void return type --- src/logging/BaseLogger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 7d015262..6ff50b98 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -28,7 +28,7 @@ export abstract class BaseLogger implements ILogger { this._platform = platform; } - log(labelOrValues: LabelOrValues, logLevel: LogLevel = LogLevel.Info) { + log(labelOrValues: LabelOrValues, logLevel: LogLevel = LogLevel.Info): void { const item = new LogItem(labelOrValues, logLevel, this); item.end = item.start; this._persistItem(item, undefined, false); From 276d8d4a42c4532fc6fcecb6dbe5b4b4f219a18f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 12:39:57 +0100 Subject: [PATCH 087/174] check for undefined, no need for ! --- src/logging/BaseLogger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 6ff50b98..2f7389ab 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -61,11 +61,11 @@ export abstract class BaseLogger implements ILogger { Errors and duration are transparently logged, also for async operations. Whatever the callback returns is returned here. */ run(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T { - if (!logLevel) { + if (logLevel === undefined) { logLevel = LogLevel.Info; } const item = new LogItem(labelOrValues, logLevel, this); - return this._run(item, callback, logLevel!, true, filterCreator); + return this._run(item, callback, logLevel, true, filterCreator); } _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: true, filterCreator?: FilterCreator): T; From 46dd78162fa9f6f467193e381e287319064c0c4a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 12:54:32 +0100 Subject: [PATCH 088/174] no need to dig into internals here --- src/logging/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 7774dd41..1d9ce20b 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -52,7 +52,7 @@ export class LogItem implements ILogItem { This is useful if the referenced operation can't be awaited. */ refDetached(logItem: ILogItem, logLevel?: LogLevel): void { logItem.ensureRefId(); - this.log({ref: (logItem as LogItem)._values.refId}, logLevel); + this.log({ref: logItem.values.refId}, logLevel); } ensureRefId(): void { From 74fb15e426c549f3ff3d29c8d9aead918298fac4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 12:54:44 +0100 Subject: [PATCH 089/174] add future todo note --- src/logging/IDBLogger.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 621359dc..96147ca1 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -51,6 +51,7 @@ export class IDBLogger extends BaseLogger { this._flushInterval = this._platform.clock.createInterval(() => this._tryFlush(), flushInterval); } + // TODO: move dispose to ILogger, listen to pagehide elsewhere and call dispose from there, which calls _finishAllAndFlush dispose(): void { window.removeEventListener("pagehide", this, false); this._flushInterval.dispose(); From afc538e87598a7bfa82c5158ce5f2c8d20353131 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 12:58:08 +0100 Subject: [PATCH 090/174] explicitly check for type, rather than truthy --- src/logging/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 1d9ce20b..0e5b0ecb 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -144,7 +144,7 @@ export class LogItem implements ILogItem { // in (v)alues, (l)abel and (t)ype are also reserved. const item: ISerializedItem = { // (s)tart - s: parentStartTime? this.start - parentStartTime : this.start, + s: typeof parentStartTime === "number" ? this.start - parentStartTime : this.start, // (d)uration d: this.duration, // (v)alues From 526a81826936eb23454ffbeccb17043bb65de69e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 13:42:49 +0100 Subject: [PATCH 091/174] only used internally --- src/logging/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/logging/types.ts b/src/logging/types.ts index 6a29a7a2..280b5606 100644 --- a/src/logging/types.ts +++ b/src/logging/types.ts @@ -46,7 +46,6 @@ export interface ILogItem { wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T; log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void; set(key: string | object, value: unknown): void; - run(callback: LogCallback): T; runDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): void; refDetached(logItem: ILogItem, logLevel?: LogLevel): void; From 42e5fb33ba9071cc8f0b47e2a335ac40299dbc47 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 13:50:56 +0100 Subject: [PATCH 092/174] remove more non-public methods from ILogItem interface --- src/logging/BaseLogger.ts | 8 ++++---- src/logging/LogItem.ts | 2 +- src/logging/types.ts | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 2f7389ab..7112c948 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -21,7 +21,7 @@ import type {ILogger, ILogExport, FilterCreator, LabelOrValues, LogCallback, ILo import type {Platform} from "../platform/web/Platform.js"; export abstract class BaseLogger implements ILogger { - protected _openItems: Set = new Set(); + protected _openItems: Set = new Set(); protected _platform: Platform; constructor({platform}) { @@ -68,10 +68,10 @@ export abstract class BaseLogger implements ILogger { return this._run(item, callback, logLevel, true, filterCreator); } - _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: true, filterCreator?: FilterCreator): T; + _run(item: LogItem, callback: LogCallback, logLevel: LogLevel, wantResult: true, filterCreator?: FilterCreator): T; // we don't return if we don't throw, as we don't have anything to return when an error is caught but swallowed for the fire-and-forget case. - _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: false, filterCreator?: FilterCreator): void; - _run(item: ILogItem, callback: LogCallback, logLevel: LogLevel, wantResult: boolean, filterCreator?: FilterCreator): T | void { + _run(item: LogItem, callback: LogCallback, logLevel: LogLevel, wantResult: false, filterCreator?: FilterCreator): void; + _run(item: LogItem, callback: LogCallback, logLevel: LogLevel, wantResult: boolean, filterCreator?: FilterCreator): T | void { this._openItems.add(item); const finishItem = () => { diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 0e5b0ecb..4fb8daa0 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -233,7 +233,7 @@ export class LogItem implements ILogItem { return err; } - child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { + child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): LogItem { if (this.end) { console.trace("log item is finished, additional logs will likely not be recorded"); } diff --git a/src/logging/types.ts b/src/logging/types.ts index 280b5606..f90572e5 100644 --- a/src/logging/types.ts +++ b/src/logging/types.ts @@ -51,8 +51,6 @@ export interface ILogItem { refDetached(logItem: ILogItem, logLevel?: LogLevel): void; ensureRefId(): void; catch(err: Error): Error; - finish(): void; - child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; serialize(filter: LogFilter, parentStartTime: number | undefined, forced: boolean): ISerializedItem | undefined; } From fde0163b978dcc1d9dfc969ad3547c8212514a23 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 13:53:27 +0100 Subject: [PATCH 093/174] remove unneeded union type and simplify code --- src/logging/LogItem.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 4fb8daa0..f9262db7 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -187,9 +187,8 @@ export class LogItem implements ILogItem { if (this.end) { console.trace("log item is finished, additional logs will likely not be recorded"); } - let result: T | Promise; try { - result = callback(this); + const result = callback(this); if (result instanceof Promise) { return result.then(promiseResult => { this.finish(); From 41a10d9697c8424ec93ca8d2d1820705cfab1161 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 13:56:20 +0100 Subject: [PATCH 094/174] explicitly check for undefined --- src/logging/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index f9262db7..cd5a5a8b 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -184,7 +184,7 @@ export class LogItem implements ILogItem { * @return {[type]} [description] */ run(callback: LogCallback): T { - if (this.end) { + if (this.end !== undefined) { console.trace("log item is finished, additional logs will likely not be recorded"); } try { From 4030a4918d13ebfa72f7d1f7205546fa3fda6a12 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 13:57:11 +0100 Subject: [PATCH 095/174] explicitly check for undefined --- src/logging/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index cd5a5a8b..da009b06 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -210,7 +210,7 @@ export class LogItem implements ILogItem { * @internal shouldn't typically be called by hand. allows to force finish if a promise is still running when closing the app */ finish(): void { - if (!this.end) { + if (this.end === undefined) { if (this._children) { for(const c of this._children) { c.finish(); From b5e9eb26baf4a0dd71f0d9c69a898fd747f59a71 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 14:33:58 +0100 Subject: [PATCH 096/174] reduce size of ILogItem interface further --- src/logging/BaseLogger.ts | 2 +- src/logging/ConsoleLogger.ts | 5 +++-- src/logging/types.ts | 12 +++++------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 7112c948..723c2f17 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -139,7 +139,7 @@ export abstract class BaseLogger implements ILogger { this._openItems.clear(); } - abstract _persistItem(item: ILogItem, filter?: LogFilter, forced?: boolean): void; + abstract _persistItem(item: LogItem, filter?: LogFilter, forced?: boolean): void; abstract export(): Promise; diff --git a/src/logging/ConsoleLogger.ts b/src/logging/ConsoleLogger.ts index f2a2efd1..f48c72b2 100644 --- a/src/logging/ConsoleLogger.ts +++ b/src/logging/ConsoleLogger.ts @@ -14,10 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ import {BaseLogger} from "./BaseLogger"; +import {LogItem} from "./LogItem"; import type {ILogItem, LogItemValues, ILogExport} from "./types"; export class ConsoleLogger extends BaseLogger { - _persistItem(item: ILogItem): void { + _persistItem(item: LogItem): void { printToConsole(item); } @@ -37,7 +38,7 @@ function filterValues(values: LogItemValues): LogItemValues | null { }, null); } -function printToConsole(item: ILogItem): void { +function printToConsole(item: LogItem): void { const label = `${itemCaption(item)} (${item.duration}ms)`; const filteredValues = filterValues(item.values); const shouldGroup = item.children || filteredValues; diff --git a/src/logging/types.ts b/src/logging/types.ts index f90572e5..01755960 100644 --- a/src/logging/types.ts +++ b/src/logging/types.ts @@ -34,15 +34,13 @@ export interface ISerializedItem { }; export interface ILogItem { - logger: any; - level: typeof LogLevel; - duration?: number; - end?: number; - start?: number; logLevel: LogLevel; - children?: Array; - values: LogItemValues; error?: Error; + readonly logger: ILogger; + readonly level: typeof LogLevel; + readonly end?: number; + readonly start?: number; + readonly values: LogItemValues; wrap(labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T; log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void; set(key: string | object, value: unknown): void; From 692ae25e76efd348f84e2946e6c2945c56467524 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 14:35:26 +0100 Subject: [PATCH 097/174] remove unused method --- src/logging/NullLogger.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/logging/NullLogger.ts b/src/logging/NullLogger.ts index ed12cd78..5ec38aa9 100644 --- a/src/logging/NullLogger.ts +++ b/src/logging/NullLogger.ts @@ -76,10 +76,6 @@ export class NullLogItem implements ILogItem { return this.refDetached(); } - run(callback: LogCallback): T { - return callback(this); - } - refDetached(): void {} ensureRefId(): void {} From 0a433b90e3ff1b5797c0de2b7b303ce4bf2bd6fe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 17 Nov 2021 15:09:25 +0100 Subject: [PATCH 098/174] packages processed by post-install need to be in dependencies, or the script fails --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b391a3c0..60b9e55c 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "finalhandler": "^1.1.1", "impunity": "^1.0.1", "mdn-polyfills": "^5.20.0", - "node-html-parser": "^4.0.0", "postcss": "^8.1.1", "postcss-css-variables": "^0.17.0", "postcss-flexbugs-fixes": "^4.2.1", @@ -55,6 +54,7 @@ "xxhashjs": "^0.2.2" }, "dependencies": { + "node-html-parser": "^4.0.0", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "@rollup/plugin-commonjs": "^15.0.0", "@rollup/plugin-json": "^4.1.0", From 7772643b0d34357dd7c9aa08c0689164c6ddf862 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 15 Nov 2021 12:45:27 +0530 Subject: [PATCH 099/174] Disposables.js --> Disposables.ts --- src/utils/{Disposables.js => Disposables.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/utils/{Disposables.js => Disposables.ts} (100%) diff --git a/src/utils/Disposables.js b/src/utils/Disposables.ts similarity index 100% rename from src/utils/Disposables.js rename to src/utils/Disposables.ts From dd74ed19570f7091769d15f20e25957758e1a4b9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 15 Nov 2021 13:08:32 +0530 Subject: [PATCH 100/174] Add types to disposeValue --- src/utils/Disposables.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/utils/Disposables.ts b/src/utils/Disposables.ts index b63cba85..6b9200f0 100644 --- a/src/utils/Disposables.ts +++ b/src/utils/Disposables.ts @@ -14,7 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -function disposeValue(value) { +type func = () => void; +type Disposable = { dispose: func; [key: string]: any } | func; + +function disposeValue(value: Disposable): void { if (typeof value === "function") { value(); } else { @@ -27,9 +30,7 @@ function isDisposable(value) { } export class Disposables { - constructor() { - this._disposables = []; - } + private readonly _disposables = []; track(disposable) { if (!isDisposable(disposable)) { From 7270918b652438be2124d178c01b325dc8c0f5a4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 15 Nov 2021 13:43:39 +0530 Subject: [PATCH 101/174] Convert Disposables to typescript --- src/utils/Disposables.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/utils/Disposables.ts b/src/utils/Disposables.ts index 6b9200f0..e05b1e87 100644 --- a/src/utils/Disposables.ts +++ b/src/utils/Disposables.ts @@ -25,14 +25,15 @@ function disposeValue(value: Disposable): void { } } -function isDisposable(value) { +function isDisposable(value: Disposable): boolean { + // todo: value can be undefined I think? return value && (typeof value === "function" || typeof value.dispose === "function"); } export class Disposables { - private readonly _disposables = []; + private _disposables: Disposable[] | null = []; - track(disposable) { + track(disposable: Disposable): Disposable { if (!isDisposable(disposable)) { throw new Error("Not a disposable"); } @@ -41,19 +42,23 @@ export class Disposables { disposeValue(disposable); return disposable; } - this._disposables.push(disposable); + this._disposables!.push(disposable); return disposable; } - untrack(disposable) { - const idx = this._disposables.indexOf(disposable); + untrack(disposable: Disposable) { + if (this.isDisposed) { + console.warn("Disposables already disposed, cannot untrack"); + return; + } + const idx = this._disposables!.indexOf(disposable); if (idx >= 0) { - this._disposables.splice(idx, 1); + this._disposables!.splice(idx, 1); } return null; } - dispose() { + dispose(): void { if (this._disposables) { for (const d of this._disposables) { disposeValue(d); @@ -62,17 +67,17 @@ export class Disposables { } } - get isDisposed() { + get isDisposed(): boolean { return this._disposables === null; } - disposeTracked(value) { + disposeTracked(value: Disposable): null { if (value === undefined || value === null || this.isDisposed) { return null; } - const idx = this._disposables.indexOf(value); + const idx = this._disposables!.indexOf(value); if (idx !== -1) { - const [foundValue] = this._disposables.splice(idx, 1); + const [foundValue] = this._disposables!.splice(idx, 1); disposeValue(foundValue); } else { console.warn("disposable not found, did it leak?", value); From 7d12c2ba54a34fb837f620ae76ba46bd25cff03d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 14:09:37 +0530 Subject: [PATCH 102/174] Add return types --- src/utils/Disposables.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils/Disposables.ts b/src/utils/Disposables.ts index e05b1e87..dc538c99 100644 --- a/src/utils/Disposables.ts +++ b/src/utils/Disposables.ts @@ -26,7 +26,6 @@ function disposeValue(value: Disposable): void { } function isDisposable(value: Disposable): boolean { - // todo: value can be undefined I think? return value && (typeof value === "function" || typeof value.dispose === "function"); } @@ -46,10 +45,10 @@ export class Disposables { return disposable; } - untrack(disposable: Disposable) { + untrack(disposable: Disposable): null { if (this.isDisposed) { console.warn("Disposables already disposed, cannot untrack"); - return; + return null; } const idx = this._disposables!.indexOf(disposable); if (idx >= 0) { From ef53a12f7ab24705d57c53178d18aa415677877c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 14:13:35 +0530 Subject: [PATCH 103/174] Fix imports --- src/domain/ViewModel.js | 2 +- src/matrix/room/timeline/Timeline.js | 2 +- src/platform/web/Platform.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/ViewModel.js b/src/domain/ViewModel.js index f0e109f8..0c665194 100644 --- a/src/domain/ViewModel.js +++ b/src/domain/ViewModel.js @@ -19,7 +19,7 @@ limitations under the License. // we do need to return a disposable from EventEmitter.on, or at least have a method here to easily track a subscription to an EventEmitter import {EventEmitter} from "../utils/EventEmitter"; -import {Disposables} from "../utils/Disposables.js"; +import {Disposables} from "../utils/Disposables"; export class ViewModel extends EventEmitter { constructor(options = {}) { diff --git a/src/matrix/room/timeline/Timeline.js b/src/matrix/room/timeline/Timeline.js index bd89ee8d..04adde0d 100644 --- a/src/matrix/room/timeline/Timeline.js +++ b/src/matrix/room/timeline/Timeline.js @@ -16,7 +16,7 @@ limitations under the License. */ import {SortedArray, AsyncMappedList, ConcatList, ObservableArray} from "../../../observable/index.js"; -import {Disposables} from "../../../utils/Disposables.js"; +import {Disposables} from "../../../utils/Disposables"; import {Direction} from "./Direction"; import {TimelineReader} from "./persistence/TimelineReader.js"; import {PendingEventEntry} from "./entries/PendingEventEntry.js"; diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index f6cd2895..3e1d6791 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -35,7 +35,7 @@ import {WorkerPool} from "./dom/WorkerPool.js"; import {BlobHandle} from "./dom/BlobHandle.js"; import {hasReadPixelPermission, ImageHandle, VideoHandle} from "./dom/ImageHandle.js"; import {downloadInIframe} from "./dom/download.js"; -import {Disposables} from "../../utils/Disposables.js"; +import {Disposables} from "../../utils/Disposables"; import {parseHTML} from "./parsehtml.js"; import {handleAvatarError} from "./ui/avatar.js"; From 4ce7634201e8fd56d503a1c3572cb89b19ba9d18 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 14:21:47 +0530 Subject: [PATCH 104/174] Convert error.js to ts --- src/matrix/error.js | 2 +- src/matrix/net/ExponentialRetryDelay.js | 2 +- src/matrix/net/RequestScheduler.js | 2 +- src/matrix/room/sending/PendingEvent.js | 2 +- src/matrix/storage/idb/utils.ts | 2 +- src/mocks/Request.js | 2 +- src/observable/ObservableValue.ts | 2 +- src/platform/web/dom/Clock.js | 2 +- src/platform/web/dom/WorkerPool.js | 2 +- src/utils/{error.js => error.ts} | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) rename src/utils/{error.js => error.ts} (96%) diff --git a/src/matrix/error.js b/src/matrix/error.js index 07144acd..0dd2b688 100644 --- a/src/matrix/error.js +++ b/src/matrix/error.js @@ -38,7 +38,7 @@ export class HomeServerError extends Error { } } -export {AbortError} from "../utils/error.js"; +export {AbortError} from "../utils/error"; export class ConnectionError extends Error { constructor(message, isTimeout) { diff --git a/src/matrix/net/ExponentialRetryDelay.js b/src/matrix/net/ExponentialRetryDelay.js index eac4bec0..853f8758 100644 --- a/src/matrix/net/ExponentialRetryDelay.js +++ b/src/matrix/net/ExponentialRetryDelay.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {AbortError} from "../../utils/error.js"; +import {AbortError} from "../../utils/error"; export class ExponentialRetryDelay { constructor(createTimeout) { diff --git a/src/matrix/net/RequestScheduler.js b/src/matrix/net/RequestScheduler.js index 53ab50ac..f9adec97 100644 --- a/src/matrix/net/RequestScheduler.js +++ b/src/matrix/net/RequestScheduler.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {AbortError} from "../../utils/error.js"; +import {AbortError} from "../../utils/error"; import {HomeServerError} from "../error.js"; import {HomeServerApi} from "./HomeServerApi.js"; import {ExponentialRetryDelay} from "./ExponentialRetryDelay.js"; diff --git a/src/matrix/room/sending/PendingEvent.js b/src/matrix/room/sending/PendingEvent.js index 01874178..4c7a0e99 100644 --- a/src/matrix/room/sending/PendingEvent.js +++ b/src/matrix/room/sending/PendingEvent.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ import {createEnum} from "../../../utils/enum.js"; -import {AbortError} from "../../../utils/error.js"; +import {AbortError} from "../../../utils/error"; import {REDACTION_TYPE} from "../common.js"; import {getRelationFromContent, getRelationTarget, setRelationTarget} from "../timeline/relations.js"; diff --git a/src/matrix/storage/idb/utils.ts b/src/matrix/storage/idb/utils.ts index ca6e06de..4ac373d2 100644 --- a/src/matrix/storage/idb/utils.ts +++ b/src/matrix/storage/idb/utils.ts @@ -17,7 +17,7 @@ limitations under the License. import { IDBRequestError } from "./error"; import { StorageError } from "../common"; -import { AbortError } from "../../../utils/error.js"; +import { AbortError } from "../../../utils/error"; let needsSyncPromise = false; diff --git a/src/mocks/Request.js b/src/mocks/Request.js index 1984f06f..14a3fabf 100644 --- a/src/mocks/Request.js +++ b/src/mocks/Request.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {AbortError} from "../utils/error.js"; +import {AbortError} from "../utils/error"; export class BaseRequest { constructor() { diff --git a/src/observable/ObservableValue.ts b/src/observable/ObservableValue.ts index 02d5fc69..b3ffa6ee 100644 --- a/src/observable/ObservableValue.ts +++ b/src/observable/ObservableValue.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {AbortError} from "../utils/error.js"; +import {AbortError} from "../utils/error"; import {BaseObservable} from "./BaseObservable"; // like an EventEmitter, but doesn't have an event type diff --git a/src/platform/web/dom/Clock.js b/src/platform/web/dom/Clock.js index 7e64de47..855e925c 100644 --- a/src/platform/web/dom/Clock.js +++ b/src/platform/web/dom/Clock.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {AbortError} from "../../../utils/error.js"; +import {AbortError} from "../../../utils/error"; class Timeout { constructor(ms) { diff --git a/src/platform/web/dom/WorkerPool.js b/src/platform/web/dom/WorkerPool.js index aeb6ca89..c36ffb78 100644 --- a/src/platform/web/dom/WorkerPool.js +++ b/src/platform/web/dom/WorkerPool.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {AbortError} from "../../../utils/error.js"; +import {AbortError} from "../../../utils/error"; class WorkerState { constructor(worker) { diff --git a/src/utils/error.js b/src/utils/error.ts similarity index 96% rename from src/utils/error.js rename to src/utils/error.ts index 820f0673..dcade6ce 100644 --- a/src/utils/error.js +++ b/src/utils/error.ts @@ -15,7 +15,7 @@ limitations under the License. */ export class AbortError extends Error { - get name() { + get name(): string { return "AbortError"; } } From a3460d8c2aa8acc58799eca1ef599b4982576b5c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 14:44:16 +0530 Subject: [PATCH 105/174] Convert formatSize to ts --- src/domain/session/room/timeline/tiles/FileTile.js | 2 +- src/utils/{formatSize.js => formatSize.ts} | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) rename src/utils/{formatSize.js => formatSize.ts} (92%) diff --git a/src/domain/session/room/timeline/tiles/FileTile.js b/src/domain/session/room/timeline/tiles/FileTile.js index c761e68e..1007d28c 100644 --- a/src/domain/session/room/timeline/tiles/FileTile.js +++ b/src/domain/session/room/timeline/tiles/FileTile.js @@ -16,7 +16,7 @@ limitations under the License. */ import {BaseMessageTile} from "./BaseMessageTile.js"; -import {formatSize} from "../../../../../utils/formatSize.js"; +import {formatSize} from "../../../../../utils/formatSize"; import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js"; export class FileTile extends BaseMessageTile { diff --git a/src/utils/formatSize.js b/src/utils/formatSize.ts similarity index 92% rename from src/utils/formatSize.js rename to src/utils/formatSize.ts index d001dacd..14ec6387 100644 --- a/src/utils/formatSize.js +++ b/src/utils/formatSize.ts @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -export function formatSize(size, decimals = 2) { + +export function formatSize(size: number, decimals: number = 2): string | undefined { if (Number.isSafeInteger(size)) { const base = Math.min(3, Math.floor(Math.log(size) / Math.log(1024))); const formattedSize = Math.round(size / Math.pow(1024, base)).toFixed(decimals); From c8eb7ea7ac911a53242c239dfe461d5450b91ee5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 15:06:19 +0530 Subject: [PATCH 106/174] Convert Lock.js to ts --- src/matrix/e2ee/olm/Decryption.js | 2 +- src/utils/{Lock.js => Lock.ts} | 26 ++++++++++++-------------- src/utils/LockMap.js | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) rename src/utils/{Lock.js => Lock.ts} (83%) diff --git a/src/matrix/e2ee/olm/Decryption.js b/src/matrix/e2ee/olm/Decryption.js index 0af3bd23..16e617a5 100644 --- a/src/matrix/e2ee/olm/Decryption.js +++ b/src/matrix/e2ee/olm/Decryption.js @@ -16,7 +16,7 @@ limitations under the License. import {DecryptionError} from "../common.js"; import {groupBy} from "../../../utils/groupBy"; -import {MultiLock} from "../../../utils/Lock.js"; +import {MultiLock} from "../../../utils/Lock"; import {Session} from "./Session.js"; import {DecryptionResult} from "../DecryptionResult.js"; diff --git a/src/utils/Lock.js b/src/utils/Lock.ts similarity index 83% rename from src/utils/Lock.js rename to src/utils/Lock.ts index 8cfc733f..ce7101de 100644 --- a/src/utils/Lock.js +++ b/src/utils/Lock.ts @@ -15,12 +15,10 @@ limitations under the License. */ export class Lock { - constructor() { - this._promise = null; - this._resolve = null; - } + private _promise: Promise | null = null; + private _resolve: (() => void) | null = null; - tryTake() { + tryTake(): boolean { if (!this._promise) { this._promise = new Promise(resolve => { this._resolve = resolve; @@ -30,17 +28,17 @@ export class Lock { return false; } - async take() { + async take(): Promise { while(!this.tryTake()) { await this.released(); } } - get isTaken() { + get isTaken(): boolean { return !!this._promise; } - release() { + release(): void { if (this._resolve) { this._promise = null; const resolve = this._resolve; @@ -49,17 +47,17 @@ export class Lock { } } - released() { + released(): Promise | null { return this._promise; } } export class MultiLock { - constructor(locks) { - this.locks = locks; + + constructor(public readonly locks: Lock[]) { } - release() { + release(): void { for (const lock of this.locks) { lock.release(); } @@ -86,9 +84,9 @@ export function tests() { lock.tryTake(); let first; - lock.released().then(() => first = lock.tryTake()); + lock.released()!.then(() => first = lock.tryTake()); let second; - lock.released().then(() => second = lock.tryTake()); + lock.released()!.then(() => second = lock.tryTake()); const promise = lock.released(); lock.release(); await promise; diff --git a/src/utils/LockMap.js b/src/utils/LockMap.js index a73dee4a..567acf4c 100644 --- a/src/utils/LockMap.js +++ b/src/utils/LockMap.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {Lock} from "./Lock.js"; +import {Lock} from "./Lock"; export class LockMap { constructor() { From 1549d8add0874737c80093918e052281822fe785 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 15:26:40 +0530 Subject: [PATCH 107/174] Convert LockMap to ts --- src/matrix/Session.js | 2 +- src/utils/{LockMap.js => LockMap.ts} | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) rename src/utils/{LockMap.js => LockMap.ts} (92%) diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 37fd90ad..874bfecd 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -34,7 +34,7 @@ import {Encryption as MegOlmEncryption} from "./e2ee/megolm/Encryption.js"; import {MEGOLM_ALGORITHM} from "./e2ee/common.js"; import {RoomEncryption} from "./e2ee/RoomEncryption.js"; import {DeviceTracker} from "./e2ee/DeviceTracker.js"; -import {LockMap} from "../utils/LockMap.js"; +import {LockMap} from "../utils/LockMap"; import {groupBy} from "../utils/groupBy"; import { keyFromCredential as ssssKeyFromCredential, diff --git a/src/utils/LockMap.js b/src/utils/LockMap.ts similarity index 92% rename from src/utils/LockMap.js rename to src/utils/LockMap.ts index 567acf4c..5952f031 100644 --- a/src/utils/LockMap.js +++ b/src/utils/LockMap.ts @@ -17,11 +17,9 @@ limitations under the License. import {Lock} from "./Lock"; export class LockMap { - constructor() { - this._map = new Map(); - } + private readonly _map: Map = new Map(); - async takeLock(key) { + async takeLock(key: unknown): Promise { let lock = this._map.get(key); if (lock) { await lock.take(); @@ -31,10 +29,10 @@ export class LockMap { this._map.set(key, lock); } // don't leave old locks lying around - lock.released().then(() => { + lock.released()!.then(() => { // give others a chance to take the lock first Promise.resolve().then(() => { - if (!lock.isTaken) { + if (!lock!.isTaken) { this._map.delete(key); } }); @@ -67,6 +65,7 @@ export function tests() { ranSecond = true; assert.equal(returnedLock.isTaken, true); // peek into internals, naughty + // @ts-ignore assert.equal(lockMap._map.get("foo"), returnedLock); }); lock.release(); @@ -84,6 +83,7 @@ export function tests() { // double delay to make sure cleanup logic ran await Promise.resolve(); await Promise.resolve(); + // @ts-ignore assert.equal(lockMap._map.has("foo"), false); }, From 88ec1b575d96f566b56bd3c6242aceaef4927575 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 15:37:32 +0530 Subject: [PATCH 108/174] Convert mergeMap.js to ts --- src/matrix/e2ee/RoomEncryption.js | 2 +- src/matrix/e2ee/megolm/decryption/DecryptionPreparation.js | 2 +- src/utils/{mergeMap.js => mergeMap.ts} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/utils/{mergeMap.js => mergeMap.ts} (93%) diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js index 430d9af3..d9151a85 100644 --- a/src/matrix/e2ee/RoomEncryption.js +++ b/src/matrix/e2ee/RoomEncryption.js @@ -16,7 +16,7 @@ limitations under the License. import {MEGOLM_ALGORITHM, DecryptionSource} from "./common.js"; import {groupEventsBySession} from "./megolm/decryption/utils"; -import {mergeMap} from "../../utils/mergeMap.js"; +import {mergeMap} from "../../utils/mergeMap"; import {groupBy} from "../../utils/groupBy"; import {makeTxnId} from "../common.js"; diff --git a/src/matrix/e2ee/megolm/decryption/DecryptionPreparation.js b/src/matrix/e2ee/megolm/decryption/DecryptionPreparation.js index 02ee32df..618955bb 100644 --- a/src/matrix/e2ee/megolm/decryption/DecryptionPreparation.js +++ b/src/matrix/e2ee/megolm/decryption/DecryptionPreparation.js @@ -15,7 +15,7 @@ limitations under the License. */ import {DecryptionChanges} from "./DecryptionChanges.js"; -import {mergeMap} from "../../../../utils/mergeMap.js"; +import {mergeMap} from "../../../../utils/mergeMap"; /** * Class that contains all the state loaded from storage to decrypt the given events diff --git a/src/utils/mergeMap.js b/src/utils/mergeMap.ts similarity index 93% rename from src/utils/mergeMap.js rename to src/utils/mergeMap.ts index a0aed207..21ad0086 100644 --- a/src/utils/mergeMap.js +++ b/src/utils/mergeMap.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -export function mergeMap(src, dst) { +export function mergeMap(src: Map | undefined, dst: Map) { if (src) { for (const [key, value] of src.entries()) { dst.set(key, value); From ea0adb440715414a6a19641cdbf2206c8910db20 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 15:41:50 +0530 Subject: [PATCH 109/174] Convert RetainedValue.js to ts --- src/matrix/room/members/MemberList.js | 2 +- src/utils/{RetainedValue.js => RetainedValue.ts} | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) rename src/utils/{RetainedValue.js => RetainedValue.ts} (82%) diff --git a/src/matrix/room/members/MemberList.js b/src/matrix/room/members/MemberList.js index de07adf5..9923fb87 100644 --- a/src/matrix/room/members/MemberList.js +++ b/src/matrix/room/members/MemberList.js @@ -15,7 +15,7 @@ limitations under the License. */ import {ObservableMap} from "../../../observable/map/ObservableMap.js"; -import {RetainedValue} from "../../../utils/RetainedValue.js"; +import {RetainedValue} from "../../../utils/RetainedValue"; export class MemberList extends RetainedValue { constructor({members, closeCallback}) { diff --git a/src/utils/RetainedValue.js b/src/utils/RetainedValue.ts similarity index 82% rename from src/utils/RetainedValue.js rename to src/utils/RetainedValue.ts index b3ed7a91..a67b848b 100644 --- a/src/utils/RetainedValue.js +++ b/src/utils/RetainedValue.ts @@ -15,16 +15,18 @@ limitations under the License. */ export class RetainedValue { - constructor(freeCallback) { + private readonly _freeCallback: () => void; + private _retentionCount: number = 1; + + constructor(freeCallback: () => void) { this._freeCallback = freeCallback; - this._retentionCount = 1; } - retain() { + retain(): void { this._retentionCount += 1; } - release() { + release(): void { this._retentionCount -= 1; if (this._retentionCount === 0) { this._freeCallback(); From 8a169d5ddc6886eb0a289ea4517bf4f2d374a8c9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 15:52:42 +0530 Subject: [PATCH 110/174] Convert sortedIndex.js to ts --- src/domain/session/room/timeline/TilesCollection.js | 2 +- src/observable/list/SortedArray.js | 2 +- src/observable/list/SortedMapList.js | 2 +- src/utils/{sortedIndex.js => sortedIndex.ts} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/utils/{sortedIndex.js => sortedIndex.ts} (93%) diff --git a/src/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js index b3696fad..8dce4125 100644 --- a/src/domain/session/room/timeline/TilesCollection.js +++ b/src/domain/session/room/timeline/TilesCollection.js @@ -15,7 +15,7 @@ limitations under the License. */ import {BaseObservableList} from "../../../../observable/list/BaseObservableList"; -import {sortedIndex} from "../../../../utils/sortedIndex.js"; +import {sortedIndex} from "../../../../utils/sortedIndex"; // maps 1..n entries to 0..1 tile. Entries are what is stored in the timeline, either an event or fragmentboundary // for now, tileCreator should be stable in whether it returns a tile or not. diff --git a/src/observable/list/SortedArray.js b/src/observable/list/SortedArray.js index 874d1b04..1201845c 100644 --- a/src/observable/list/SortedArray.js +++ b/src/observable/list/SortedArray.js @@ -15,7 +15,7 @@ limitations under the License. */ import {BaseObservableList} from "./BaseObservableList"; -import {sortedIndex} from "../../utils/sortedIndex.js"; +import {sortedIndex} from "../../utils/sortedIndex"; import {findAndUpdateInArray} from "./common"; export class SortedArray extends BaseObservableList { diff --git a/src/observable/list/SortedMapList.js b/src/observable/list/SortedMapList.js index 2421419e..38900380 100644 --- a/src/observable/list/SortedMapList.js +++ b/src/observable/list/SortedMapList.js @@ -15,7 +15,7 @@ limitations under the License. */ import {BaseObservableList} from "./BaseObservableList"; -import {sortedIndex} from "../../utils/sortedIndex.js"; +import {sortedIndex} from "../../utils/sortedIndex"; /* diff --git a/src/utils/sortedIndex.js b/src/utils/sortedIndex.ts similarity index 93% rename from src/utils/sortedIndex.js rename to src/utils/sortedIndex.ts index e0acb5ff..0022cb87 100644 --- a/src/utils/sortedIndex.js +++ b/src/utils/sortedIndex.ts @@ -22,7 +22,7 @@ limitations under the License. * Based on Underscore.js 1.8.3 * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors */ -export function sortedIndex(array, value, comparator) { +export function sortedIndex(array: T[], value: T, comparator: (x:T, y:T) => number): number { let low = 0; let high = array.length; From afecac3e3cd8b851c8a2df90b9556ea6f01a54a2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 16 Nov 2021 16:14:13 +0530 Subject: [PATCH 111/174] Convert timeout.js to ts --- src/platform/web/dom/request/fetch.js | 2 +- src/utils/{timeout.js => timeout.ts} | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) rename src/utils/{timeout.js => timeout.ts} (88%) diff --git a/src/platform/web/dom/request/fetch.js b/src/platform/web/dom/request/fetch.js index 66f1a148..adf833ef 100644 --- a/src/platform/web/dom/request/fetch.js +++ b/src/platform/web/dom/request/fetch.js @@ -19,7 +19,7 @@ import { AbortError, ConnectionError } from "../../../../matrix/error.js"; -import {abortOnTimeout} from "../../../../utils/timeout.js"; +import {abortOnTimeout} from "../../../../utils/timeout"; import {addCacheBuster} from "./common.js"; import {xhrRequest} from "./xhr.js"; diff --git a/src/utils/timeout.js b/src/utils/timeout.ts similarity index 88% rename from src/utils/timeout.js rename to src/utils/timeout.ts index 6bfc0d7e..96efc10d 100644 --- a/src/utils/timeout.js +++ b/src/utils/timeout.ts @@ -16,9 +16,13 @@ limitations under the License. */ import {ConnectionError} from "../matrix/error.js"; +import type {Timeout} from "../platform/web/dom/Clock.js" +type TimeoutCreator = (ms: number) => Timeout; +// ts-todo: export type RequestResult from fetch.js? we'll need to wait until it's typescript though. +type Abortable = { abort(): void; [key: string]: any }; -export function abortOnTimeout(createTimeout, timeoutAmount, requestResult, responsePromise) { +export function abortOnTimeout(createTimeout: TimeoutCreator, timeoutAmount: number, requestResult: Abortable, responsePromise: Promise) { const timeout = createTimeout(timeoutAmount); // abort request if timeout finishes first let timedOut = false; From 6c2aa1bf61fce74bb3c80d73a2d71b784740a627 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 14:04:43 +0530 Subject: [PATCH 112/174] Convert hkdf.js to ts --- src/platform/web/LegacyPlatform.js | 2 +- src/utils/crypto/{hkdf.js => hkdf.ts} | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) rename src/utils/crypto/{hkdf.js => hkdf.ts} (83%) diff --git a/src/platform/web/LegacyPlatform.js b/src/platform/web/LegacyPlatform.js index e6bf7774..5cbfb52f 100644 --- a/src/platform/web/LegacyPlatform.js +++ b/src/platform/web/LegacyPlatform.js @@ -15,7 +15,7 @@ limitations under the License. */ import aesjs from "../../../lib/aes-js/index.js"; -import {hkdf} from "../../utils/crypto/hkdf.js"; +import {hkdf} from "../../utils/crypto/hkdf"; import {Platform as ModernPlatform} from "./Platform.js"; export function Platform(container, paths) { diff --git a/src/utils/crypto/hkdf.js b/src/utils/crypto/hkdf.ts similarity index 83% rename from src/utils/crypto/hkdf.js rename to src/utils/crypto/hkdf.ts index d46dc496..25c1faf6 100644 --- a/src/utils/crypto/hkdf.js +++ b/src/utils/crypto/hkdf.ts @@ -6,8 +6,10 @@ * Based on https://github.com/junkurihara/jscu/blob/develop/packages/js-crypto-hkdf/src/hkdf.ts */ +import type {Crypto} from "../../platform/web/dom/Crypto.js"; + // forked this code to make it use the cryptoDriver for HMAC that is more backwards-compatible -export async function hkdf(cryptoDriver, key, salt, info, hash, length) { +export async function hkdf(cryptoDriver: Crypto, key: Uint8Array, salt: Uint8Array, info: Uint8Array, hash: string, length: number): Promise { length = length / 8; const len = cryptoDriver.digestSize(hash); From a945edfe07808867f87191ae4cce8c394aa26315 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 14:24:30 +0530 Subject: [PATCH 113/174] Convert pbkdf2.js to ts --- src/utils/crypto/{pbkdf2.js => pbkdf2.ts} | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) rename src/utils/crypto/{pbkdf2.js => pbkdf2.ts} (83%) diff --git a/src/utils/crypto/pbkdf2.js b/src/utils/crypto/pbkdf2.ts similarity index 83% rename from src/utils/crypto/pbkdf2.js rename to src/utils/crypto/pbkdf2.ts index 5a239791..6a8b8495 100644 --- a/src/utils/crypto/pbkdf2.js +++ b/src/utils/crypto/pbkdf2.ts @@ -6,17 +6,19 @@ * Based on https://github.com/junkurihara/jscu/blob/develop/packages/js-crypto-pbkdf/src/pbkdf.ts */ +import type {Crypto} from "../../platform/web/dom/Crypto.js"; + // not used atm, but might in the future // forked this code to make it use the cryptoDriver for HMAC that is more backwards-compatible -const nwbo = (num, len) => { +const nwbo = (num: number, len: number): Uint8Array => { const arr = new Uint8Array(len); for(let i=0; i> ((len - i - 1)*8)); return arr; }; -export async function pbkdf2(cryptoDriver, password, iterations, salt, hash, length) { +export async function pbkdf2(cryptoDriver: Crypto, password: Uint8Array, iterations: number, salt: Uint8Array, hash: string, length: number): Promise { const dkLen = length / 8; if (iterations <= 0) { throw new Error('InvalidIterationCount'); @@ -30,7 +32,7 @@ export async function pbkdf2(cryptoDriver, password, iterations, salt, hash, len const l = Math.ceil(dkLen/hLen); const r = dkLen - (l-1)*hLen; - const funcF = async (i) => { + const funcF = async (i: number) => { const seed = new Uint8Array(salt.length + 4); seed.set(salt); seed.set(nwbo(i+1, 4), salt.length); @@ -46,7 +48,7 @@ export async function pbkdf2(cryptoDriver, password, iterations, salt, hash, len return {index: i, value: outputF}; }; - const Tis = []; + const Tis: Promise<{index: number, value: Uint8Array}>[] = []; const DK = new Uint8Array(dkLen); for(let i = 0; i < l; i++) { Tis.push(funcF(i)); From 0e18247184f0d041659cc46b07da197b17569bea Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 14:36:15 +0530 Subject: [PATCH 114/174] Use constant type --- src/utils/crypto/hkdf.ts | 2 +- src/utils/crypto/pbkdf2.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/crypto/hkdf.ts b/src/utils/crypto/hkdf.ts index 25c1faf6..44f66029 100644 --- a/src/utils/crypto/hkdf.ts +++ b/src/utils/crypto/hkdf.ts @@ -9,7 +9,7 @@ import type {Crypto} from "../../platform/web/dom/Crypto.js"; // forked this code to make it use the cryptoDriver for HMAC that is more backwards-compatible -export async function hkdf(cryptoDriver: Crypto, key: Uint8Array, salt: Uint8Array, info: Uint8Array, hash: string, length: number): Promise { +export async function hkdf(cryptoDriver: Crypto, key: Uint8Array, salt: Uint8Array, info: Uint8Array, hash: "SHA-256" | "SHA-512", length: number): Promise { length = length / 8; const len = cryptoDriver.digestSize(hash); diff --git a/src/utils/crypto/pbkdf2.ts b/src/utils/crypto/pbkdf2.ts index 6a8b8495..a9108377 100644 --- a/src/utils/crypto/pbkdf2.ts +++ b/src/utils/crypto/pbkdf2.ts @@ -18,7 +18,7 @@ const nwbo = (num: number, len: number): Uint8Array => { return arr; }; -export async function pbkdf2(cryptoDriver: Crypto, password: Uint8Array, iterations: number, salt: Uint8Array, hash: string, length: number): Promise { +export async function pbkdf2(cryptoDriver: Crypto, password: Uint8Array, iterations: number, salt: Uint8Array, hash: "SHA-256" | "SHA-512", length: number): Promise { const dkLen = length / 8; if (iterations <= 0) { throw new Error('InvalidIterationCount'); From ebd1caf6d19583ece0e6795a109bc38fa7ce6b5b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 15:50:04 +0530 Subject: [PATCH 115/174] Convert enum.js to ts --- src/utils/{enum.js => enum.ts} | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) rename src/utils/{enum.js => enum.ts} (80%) diff --git a/src/utils/enum.js b/src/utils/enum.ts similarity index 80% rename from src/utils/enum.js rename to src/utils/enum.ts index 4defcfd7..71365984 100644 --- a/src/utils/enum.js +++ b/src/utils/enum.ts @@ -14,12 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -export function createEnum(...values) { +export function createEnum(...values: string[]): Readonly<{}> { const obj = {}; for (const value of values) { - if (typeof value !== "string") { - throw new Error("Invalid enum value name" + value?.toString()); - } obj[value] = value; } return Object.freeze(obj); From 0c424cb77f054bc5706b49325683fb12c654f476 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 15:54:44 +0530 Subject: [PATCH 116/174] Fix imports --- src/domain/session/SessionStatusViewModel.js | 2 +- src/domain/session/room/timeline/tiles/BaseTextTile.js | 2 +- src/domain/session/settings/SessionBackupViewModel.js | 2 +- src/matrix/SessionContainer.js | 2 +- src/matrix/Sync.js | 2 +- src/matrix/e2ee/common.js | 2 +- src/matrix/net/Reconnector.js | 2 +- src/matrix/room/sending/PendingEvent.js | 2 +- src/matrix/ssss/index.js | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/domain/session/SessionStatusViewModel.js b/src/domain/session/SessionStatusViewModel.js index 108d2aca..27dad7cc 100644 --- a/src/domain/session/SessionStatusViewModel.js +++ b/src/domain/session/SessionStatusViewModel.js @@ -15,7 +15,7 @@ limitations under the License. */ import {ViewModel} from "../ViewModel.js"; -import {createEnum} from "../../utils/enum.js"; +import {createEnum} from "../../utils/enum"; import {ConnectionStatus} from "../../matrix/net/Reconnector.js"; import {SyncStatus} from "../../matrix/Sync.js"; diff --git a/src/domain/session/room/timeline/tiles/BaseTextTile.js b/src/domain/session/room/timeline/tiles/BaseTextTile.js index fb61cb4b..60024ca6 100644 --- a/src/domain/session/room/timeline/tiles/BaseTextTile.js +++ b/src/domain/session/room/timeline/tiles/BaseTextTile.js @@ -16,7 +16,7 @@ limitations under the License. import {BaseMessageTile} from "./BaseMessageTile.js"; import {stringAsBody} from "../MessageBody.js"; -import {createEnum} from "../../../../../utils/enum.js"; +import {createEnum} from "../../../../../utils/enum"; export const BodyFormat = createEnum("Plain", "Html"); diff --git a/src/domain/session/settings/SessionBackupViewModel.js b/src/domain/session/settings/SessionBackupViewModel.js index 533d0f6d..52be43b4 100644 --- a/src/domain/session/settings/SessionBackupViewModel.js +++ b/src/domain/session/settings/SessionBackupViewModel.js @@ -16,7 +16,7 @@ limitations under the License. import {ViewModel} from "../../ViewModel.js"; import {KeyType} from "../../../matrix/ssss/index.js"; -import {createEnum} from "../../../utils/enum.js"; +import {createEnum} from "../../../utils/enum"; export const Status = createEnum("Enabled", "SetupKey", "SetupPhrase", "Pending"); diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 3e3dab66..dc9f9ee8 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {createEnum} from "../utils/enum.js"; +import {createEnum} from "../utils/enum"; import {lookupHomeserver} from "./well-known.js"; import {AbortableOperation} from "../utils/AbortableOperation"; import {ObservableValue} from "../observable/ObservableValue"; diff --git a/src/matrix/Sync.js b/src/matrix/Sync.js index 0be48007..de09a96d 100644 --- a/src/matrix/Sync.js +++ b/src/matrix/Sync.js @@ -16,7 +16,7 @@ limitations under the License. */ import {ObservableValue} from "../observable/ObservableValue"; -import {createEnum} from "../utils/enum.js"; +import {createEnum} from "../utils/enum"; const INCREMENTAL_TIMEOUT = 30000; diff --git a/src/matrix/e2ee/common.js b/src/matrix/e2ee/common.js index fa970236..775e3dd0 100644 --- a/src/matrix/e2ee/common.js +++ b/src/matrix/e2ee/common.js @@ -15,7 +15,7 @@ limitations under the License. */ import anotherjson from "../../../lib/another-json/index.js"; -import {createEnum} from "../../utils/enum.js"; +import {createEnum} from "../../utils/enum"; export const DecryptionSource = createEnum("Sync", "Timeline", "Retry"); diff --git a/src/matrix/net/Reconnector.js b/src/matrix/net/Reconnector.js index 6fd2ca94..6eaa78d7 100644 --- a/src/matrix/net/Reconnector.js +++ b/src/matrix/net/Reconnector.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {createEnum} from "../../utils/enum.js"; +import {createEnum} from "../../utils/enum"; import {ObservableValue} from "../../observable/ObservableValue"; export const ConnectionStatus = createEnum( diff --git a/src/matrix/room/sending/PendingEvent.js b/src/matrix/room/sending/PendingEvent.js index 4c7a0e99..d4c10704 100644 --- a/src/matrix/room/sending/PendingEvent.js +++ b/src/matrix/room/sending/PendingEvent.js @@ -13,7 +13,7 @@ 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 {createEnum} from "../../../utils/enum.js"; +import {createEnum} from "../../../utils/enum"; import {AbortError} from "../../../utils/error"; import {REDACTION_TYPE} from "../common.js"; import {getRelationFromContent, getRelationTarget, setRelationTarget} from "../timeline/relations.js"; diff --git a/src/matrix/ssss/index.js b/src/matrix/ssss/index.js index cb795766..b063ab0b 100644 --- a/src/matrix/ssss/index.js +++ b/src/matrix/ssss/index.js @@ -18,7 +18,7 @@ import {KeyDescription, Key} from "./common.js"; import {keyFromPassphrase} from "./passphrase.js"; import {keyFromRecoveryKey} from "./recoveryKey.js"; import {SESSION_E2EE_KEY_PREFIX} from "../e2ee/common.js"; -import {createEnum} from "../../utils/enum.js"; +import {createEnum} from "../../utils/enum"; const SSSS_KEY = `${SESSION_E2EE_KEY_PREFIX}ssssKey`; From 1beb153f21fa27b6cd9013b6f125231af493f013 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 16:26:19 +0530 Subject: [PATCH 117/174] func --> Func --- src/utils/Disposables.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/Disposables.ts b/src/utils/Disposables.ts index dc538c99..0317825b 100644 --- a/src/utils/Disposables.ts +++ b/src/utils/Disposables.ts @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -type func = () => void; -type Disposable = { dispose: func; [key: string]: any } | func; +type Func = () => void; +type Disposable = { dispose: Func; [key: string]: any } | Func; function disposeValue(value: Disposable): void { if (typeof value === "function") { From 5a0c06473c215ebb86ba09c0373e6f82e9f6eb50 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 16:28:14 +0530 Subject: [PATCH 118/174] Use undefined instead of null --- src/utils/Lock.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/Lock.ts b/src/utils/Lock.ts index ce7101de..238d88f9 100644 --- a/src/utils/Lock.ts +++ b/src/utils/Lock.ts @@ -15,8 +15,8 @@ limitations under the License. */ export class Lock { - private _promise: Promise | null = null; - private _resolve: (() => void) | null = null; + private _promise?: Promise; + private _resolve?: (() => void); tryTake(): boolean { if (!this._promise) { @@ -40,14 +40,14 @@ export class Lock { release(): void { if (this._resolve) { - this._promise = null; + this._promise = undefined; const resolve = this._resolve; - this._resolve = null; + this._resolve = undefined; resolve(); } } - released(): Promise | null { + released(): Promise | undefined { return this._promise; } } From 08ef84d1121cc6b22d8421b06ad99d4f93cf1100 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 16:33:37 +0530 Subject: [PATCH 119/174] Mention return type --- src/utils/mergeMap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/mergeMap.ts b/src/utils/mergeMap.ts index 21ad0086..b5d9b524 100644 --- a/src/utils/mergeMap.ts +++ b/src/utils/mergeMap.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -export function mergeMap(src: Map | undefined, dst: Map) { +export function mergeMap(src: Map | undefined, dst: Map): void { if (src) { for (const [key, value] of src.entries()) { dst.set(key, value); From a14a8c3a07cbdbcf7b901e789a44ab0617e93132 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Wed, 17 Nov 2021 19:40:21 +0530 Subject: [PATCH 120/174] Create interface IDisposable Co-authored-by: Bruno Windels --- src/utils/Disposables.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/Disposables.ts b/src/utils/Disposables.ts index 0317825b..9671665f 100644 --- a/src/utils/Disposables.ts +++ b/src/utils/Disposables.ts @@ -15,7 +15,11 @@ limitations under the License. */ type Func = () => void; -type Disposable = { dispose: Func; [key: string]: any } | Func; +export interface IDisposable { + dispose(): void; +} + +type Disposable = IDisposable | (() => void); function disposeValue(value: Disposable): void { if (typeof value === "function") { From 048547828d5e70e93a3b107ada97d30253e42778 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 19:41:03 +0530 Subject: [PATCH 121/174] Remove type Func --- src/utils/Disposables.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/Disposables.ts b/src/utils/Disposables.ts index 9671665f..19a5983c 100644 --- a/src/utils/Disposables.ts +++ b/src/utils/Disposables.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -type Func = () => void; export interface IDisposable { dispose(): void; } From 64a9892ee20e3440e42dbb1028cacdca004c07f7 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 19:44:19 +0530 Subject: [PATCH 122/174] Use generic T in LockMap --- src/utils/LockMap.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/LockMap.ts b/src/utils/LockMap.ts index 5952f031..4c7cba13 100644 --- a/src/utils/LockMap.ts +++ b/src/utils/LockMap.ts @@ -16,10 +16,10 @@ limitations under the License. import {Lock} from "./Lock"; -export class LockMap { - private readonly _map: Map = new Map(); +export class LockMap { + private readonly _map: Map = new Map(); - async takeLock(key: unknown): Promise { + async takeLock(key: T): Promise { let lock = this._map.get(key); if (lock) { await lock.take(); From ea2842f37fc907f29980afe84cbcdb3a8e8ca4a3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 19:57:38 +0530 Subject: [PATCH 123/174] Return empty string --- src/utils/formatSize.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/formatSize.ts b/src/utils/formatSize.ts index 14ec6387..f611c79e 100644 --- a/src/utils/formatSize.ts +++ b/src/utils/formatSize.ts @@ -15,7 +15,7 @@ limitations under the License. */ -export function formatSize(size: number, decimals: number = 2): string | undefined { +export function formatSize(size: number, decimals: number = 2): string { if (Number.isSafeInteger(size)) { const base = Math.min(3, Math.floor(Math.log(size) / Math.log(1024))); const formattedSize = Math.round(size / Math.pow(1024, base)).toFixed(decimals); @@ -26,4 +26,5 @@ export function formatSize(size: number, decimals: number = 2): string | undefin case 3: return `${formattedSize} GB`; } } + return ""; } From 8fcfd713e07e8a04f4f2f39abf056cf45da9298f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 17 Nov 2021 20:27:23 +0530 Subject: [PATCH 124/174] Use IAbortable --- src/utils/AbortableOperation.ts | 2 +- src/utils/timeout.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/utils/AbortableOperation.ts b/src/utils/AbortableOperation.ts index 0cc49e10..d03f820a 100644 --- a/src/utils/AbortableOperation.ts +++ b/src/utils/AbortableOperation.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -interface IAbortable { +export interface IAbortable { abort(); } diff --git a/src/utils/timeout.ts b/src/utils/timeout.ts index 96efc10d..f0460047 100644 --- a/src/utils/timeout.ts +++ b/src/utils/timeout.ts @@ -17,12 +17,11 @@ limitations under the License. import {ConnectionError} from "../matrix/error.js"; import type {Timeout} from "../platform/web/dom/Clock.js" +import type {IAbortable} from "./AbortableOperation"; type TimeoutCreator = (ms: number) => Timeout; -// ts-todo: export type RequestResult from fetch.js? we'll need to wait until it's typescript though. -type Abortable = { abort(): void; [key: string]: any }; -export function abortOnTimeout(createTimeout: TimeoutCreator, timeoutAmount: number, requestResult: Abortable, responsePromise: Promise) { +export function abortOnTimeout(createTimeout: TimeoutCreator, timeoutAmount: number, requestResult: IAbortable, responsePromise: Promise) { const timeout = createTimeout(timeoutAmount); // abort request if timeout finishes first let timedOut = false; From 6980921dab5381448381c5b5d32e513946cf9493 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 18 Nov 2021 14:16:10 +0000 Subject: [PATCH 125/174] some impl notes for SDK --- doc/impl-thoughts/SDK.md | 97 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 doc/impl-thoughts/SDK.md diff --git a/doc/impl-thoughts/SDK.md b/doc/impl-thoughts/SDK.md new file mode 100644 index 00000000..0faea2f2 --- /dev/null +++ b/doc/impl-thoughts/SDK.md @@ -0,0 +1,97 @@ +SDK: + + - we need to compile src/lib.ts to javascript, with a d.ts file generated as well. We need to compile to javascript once for cjs and once of es modules. The package.json looks like this: + +we don't need to bundle for the sdk case! we might need to do some transpilation to just plain ES6 (e.g. don't assume ?. and ??) we could use a browserslist query for this e.g. `node 14`. esbuild seems to support this as well, tldraw uses esbuild for their build. + +one advantage of not bundling the files for the sdk is that you can still use import overrides in the consuming project build settings. is that an idiomatic way of doing things though? + + + +``` +"main": "./dist/index.cjs", + "exports": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + }, + "types": "dist/index.d.ts", +``` + +this way we will support typescript, non-esm javascript and esm javascript using libhydrogen as an SDK + +got this from https://medium.com/dazn-tech/publishing-npm-packages-as-native-es-modules-41ffbc0a9dea + +how about the assets? + +we also need to build the app + +we need to be able to version libhydrogen independently from hydrogen the app? as any api breaking changes will need a major version increase. we probably want to end up with a monorepo where the app uses the sdk as well and we just use the local code with yarn link? + +## Assets + +we want to provide scss/sass files, but also css that can be included +https://github.com/webpack/webpack/issues/7353 seems to imply that we just need to include the assets in the published files and from there on it is the consumer of libhydrogen's problem. + + +how does all of this tie in with vite? + + +we want to have hydrogenapp be a consumer of libhydrogen, potentially as two packages in a monorepo ... but we want the SDK to expose views and stylesheets... without having an index.html (which would be in hydrogenapp). this seems a bit odd...? + +what would be in hydrogenapp actually? just an index.html file? + +I'm not sure it makes sense to have them be 2 different packages in a monorepo, they should really be two artifacts from the same directory. + +the stylesheets included in libhydrogen are from the same main.css file as is used in the app + +https://www.freecodecamp.org/news/build-a-css-library-with-vitejs/ + +basically, we import the sass file from src/lib.ts so it is included in the assets there too, and we also create a plugin that emits a file for every sass file as suggested in the link above? + +we probably want two different build commands for the app and the sdk though, we could have a parent vite config that both build configs extend from? + + +### Dependency assets +our dependencies should not be bundled for the SDK case. So if we import aesjs, it would be up to the build system of the consuming project to make that import work. + +the paths.ts thingy ... we want to make it easy for people to setup the assets for our dependencies (olm), some assets are also part of the sdk itself. it might make sense to make all of the assets there part of the sdk (e.g. bundle olm.wasm and friends?) although shipping crypto, etc ... + +perhaps we should have an include file per build system that treats own assets and dep assets the same by including the package name as wel for our own deps: +```js +import _downloadSandboxPath from "@matrix-org/hydrogen-sdk/download-sandbox.html?url"; +import _serviceWorkerPath from "@matrix-org/hydrogen-sdk/sw.js?url"; // not yet sure this is the way to do it +import olmWasmPath from "@matrix-org/olm/olm.wasm?url"; +import olmJsPath from "@matrix-org/olm/olm.js?url"; +import olmLegacyJsPath from "@matrix-org/olm/olm_legacy.js?url"; + +export const olmPaths = { + wasm: olmWasmPath, + legacyBundle: olmLegacyJsPath, + wasmBundle: olmJsPath, +}; + +export const downloadSandboxPath = _downloadSandboxPath; +``` + +we could put this file per build system, as ESM, in dist as well so you can include it to get the paths + + +## Tooling + + - `vite` a more high-level build tool that takes your index.html and turns it into optimized assets that you can host for production, as well as a very fast dev server. is used to have good default settings for our tools, typescript support, and also deals with asset compiling. good dev server. Would be nice to have the same tool for dev and prod. vite has good support for using `import` for anything that is not javascript, where we had an issue with `snowpack` (to get the prod path of an asset). + - `rollup`: inlines + - `lerna` is used to handle multi-package monorepos + - `esbuild`: a js/ts build tool that we could use for building the lower level sdk where no other assets are involved, `vite` uses it for fast dev builds (`rollup` for prod). For now we won't extract a lower level sdk though. + + +## TODO + + - finish vite app build (without IE11 for now?) + - create vite config to build src/lib.ts in cjs and esm, inheriting from a common base config with the app config + - this will create a dist folder with + - the whole source tree in es and cjs format + - an es file to import get the asset paths as they are expected by Platform, per build system + - assets from hydrogen itself: + - css files and any resource used therein + - download-sandbox.html + - a type declaration file (index.d.ts) From f23227fc8b385bc9d72f7b51c272a59f4fef9e2b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 19 Nov 2021 10:22:59 +0100 Subject: [PATCH 126/174] use latest version of impunity with support for multiple entry points --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b391a3c0..7f584ee3 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint": "^7.32.0", "fake-indexeddb": "^3.1.2", "finalhandler": "^1.1.1", - "impunity": "^1.0.1", + "impunity": "^1.0.3", "mdn-polyfills": "^5.20.0", "node-html-parser": "^4.0.0", "postcss": "^8.1.1", From ec71e30ecb76551ec6fdb2e3c96ae3aee8eae9e0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 19 Nov 2021 10:23:18 +0100 Subject: [PATCH 127/174] add Platform as entry point so also platform dependant code gets searched for tests --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f584ee3..d7d0bc5d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "lint": "eslint --cache src/", "lint-ts": "eslint src/ -c .ts-eslintrc.js --ext .ts", "lint-ci": "eslint src/", - "test": "impunity --entry-point src/main.js --force-esm-dirs lib/ src/", + "test": "impunity --entry-point src/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/", "start": "snowpack dev --port 3000", "build": "node --experimental-modules scripts/build.mjs", "postinstall": "node ./scripts/post-install.js" From f1a6a4924ec1a0c703f8437a228df1e2d9b850e0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 19 Nov 2021 10:23:59 +0100 Subject: [PATCH 128/174] commit yarn.lock too --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index fc159a40..830a5cd5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3021,10 +3021,10 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -impunity@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/impunity/-/impunity-1.0.1.tgz#e1d19c468bd7b8ae3dc7ad44ccbe81f17c626fde" - integrity sha512-MbQ6+UcLNdpMZPBYvr0OLY3pQCr8UMjMK45YCIrRFsZUO9hj/eMurLjpjiO3HBgCkZcw+5FlU6JCHjm5Q3u7mw== +impunity@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/impunity/-/impunity-1.0.3.tgz#1e9972d137f0e0dbed4d39e6eca6c238a29b6526" + integrity sha512-D3XT8ED/AX5mbSQdAf0nz+65ilZE/q17Yykx8g7FqlnKjDdoowFv9kxdS4tOggPrJY/5lAfE86/ZFa878VpZBw== dependencies: colors "^1.3.3" commander "^6.1.0" From d4e923f9de02168e2e049cf83932aaf41a0ac8cc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sat, 24 Jul 2021 13:42:57 +0530 Subject: [PATCH 129/174] Remove code from loadList We don't need this method so best to leave it empty. Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index 4426d97c..b6b4b0d1 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -168,6 +168,8 @@ export class LazyListView extends ListView { mount() { const root = super.mount(); + this._subscription = this._list.subscribe(this); + this._childInstances = []; this._parent = el("div", {className: "LazyListParent"}, root); /* Hooking to scroll events can be expensive. @@ -181,17 +183,12 @@ export class LazyListView extends ListView { update(attributes) { this._renderRange = null; super.update(attributes); + this._childInstances = []; this._initialRender(); } loadList() { - if (!this._list) { return; } - this._subscription = this._list.subscribe(this); - this._childInstances = []; - /* - super.loadList() would render the entire list at this point. - We instead lazy render a part of the list in _renderIfNeeded - */ + // We don't render the entire list; so nothing to see here. } _removeChild(child) { From 1a28b4f887eb49841e53e5ee078aa9bc1f6d93f8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 25 Jul 2021 19:48:51 +0530 Subject: [PATCH 130/174] WIP --- src/platform/web/ui/general/LazyListView.js | 38 +++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index b6b4b0d1..a8c686eb 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -19,6 +19,11 @@ import {mountView} from "./utils"; import {ListView} from "./ListView"; import {insertAt} from "./utils"; +class ScrollDirection { + static get downwards() { return 1; } + static get upwards() { return -1; } +} + class ItemRange { constructor(topCount, renderCount, bottomCount) { this.topCount = topCount; @@ -56,6 +61,10 @@ class ItemRange { ); } + get lastIndex() { + return this.topCount + this.renderCount; + } + totalSize() { return this.topCount + this.renderCount + this.bottomCount; } @@ -69,6 +78,23 @@ class ItemRange { */ return idx - this.topCount; } + + scrollDirectionTo(range) { + return range.bottomCount < this.bottomCount ? ScrollDirection.downwards : ScrollDirection.upwards; + } + + diff(range) { + const diffResult = {}; + if (this.scrollDirectionTo(range) === ScrollDirection.downwards) { + diffResult.toRemove = { start: this.topCount, end: range.topCount - 1 }; + diffResult.toAdd = { start: this.lastIndex + 1, end: range.lastIndex }; + } + else { + diffResult.toRemove = { start: range.lastIndex + 1, end: this.lastIndex }; + diffResult.toAdd = { start: range.topCount, end: this.topCount - 1 }; + } + return diffResult; + } } export class LazyListView extends ListView { @@ -101,6 +127,9 @@ export class LazyListView extends ListView { // only update render Range if the new range + overflowMargin isn't contained by the old anymore // or if we are force rendering if (forceRender || !this._renderRange.contains(intersectRange)) { + console.log("new", renderRange); + console.log("current", this._renderRange); + console.log("diff", this._renderRange.diff(renderRange)); this._renderRange = renderRange; this._renderElementsInRange(); } @@ -146,13 +175,18 @@ export class LazyListView extends ListView { return null; } - _renderElementsInRange() { + _adjustPadding() { + + } + + _renderElementsInRange(range) { const { topCount, renderCount, bottomCount } = this._renderRange; + const renderedItems = this._itemsFromList(topCount, topCount + renderCount); const paddingTop = topCount * this._itemHeight; const paddingBottom = bottomCount * this._itemHeight; - const renderedItems = this._itemsFromList(topCount, topCount + renderCount); this._root.style.paddingTop = `${paddingTop}px`; this._root.style.paddingBottom = `${paddingBottom}px`; + for (const child of this._childInstances) { this._removeChild(child); } From 61402e798e878da08d5b3d4e6873f047790bd67a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 27 Jul 2021 13:24:36 +0530 Subject: [PATCH 131/174] WIP 2 --- src/platform/web/ui/general/LazyListView.js | 88 +++++++++++++++------ 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index a8c686eb..299e511c 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -84,16 +84,17 @@ class ItemRange { } diff(range) { - const diffResult = {}; - if (this.scrollDirectionTo(range) === ScrollDirection.downwards) { - diffResult.toRemove = { start: this.topCount, end: range.topCount - 1 }; - diffResult.toAdd = { start: this.lastIndex + 1, end: range.lastIndex }; + const scrollDirection = this.scrollDirectionTo(range); + let toRemove, toAdd; + if (scrollDirection === ScrollDirection.downwards) { + toRemove = { start: this.topCount, end: range.topCount - 1 }; + toAdd = { start: this.lastIndex, end: range.lastIndex }; } else { - diffResult.toRemove = { start: range.lastIndex + 1, end: this.lastIndex }; - diffResult.toAdd = { start: range.topCount, end: this.topCount - 1 }; + toRemove = { start: range.lastIndex, end: this.lastIndex }; + toAdd = { start: range.topCount, end: this.topCount - 1 }; } - return diffResult; + return { toRemove, toAdd, scrollDirection }; } } @@ -130,8 +131,7 @@ export class LazyListView extends ListView { console.log("new", renderRange); console.log("current", this._renderRange); console.log("diff", this._renderRange.diff(renderRange)); - this._renderRange = renderRange; - this._renderElementsInRange(); + this._renderElementsInRange(renderRange); } } @@ -149,7 +149,18 @@ export class LazyListView extends ListView { const range = this._getVisibleRange(); const renderRange = range.expand(this._overflowItems); this._renderRange = renderRange; - this._renderElementsInRange(); + + const { topCount, renderCount } = this._renderRange; + const renderedItems = this._itemsFromList(topCount, topCount + renderCount); + this._adjustPadding(renderRange); + this._childInstances = []; + const fragment = document.createDocumentFragment(); + for (const item of renderedItems) { + const view = this._childCreator(item); + this._childInstances.push(view); + fragment.appendChild(mountView(view, this._mountArgs)); + } + this._root.appendChild(fragment); } _itemsFromList(start, end) { @@ -175,29 +186,58 @@ export class LazyListView extends ListView { return null; } - _adjustPadding() { - - } - - _renderElementsInRange(range) { - const { topCount, renderCount, bottomCount } = this._renderRange; - const renderedItems = this._itemsFromList(topCount, topCount + renderCount); + _adjustPadding(range) { + const { topCount, bottomCount } = range; const paddingTop = topCount * this._itemHeight; const paddingBottom = bottomCount * this._itemHeight; this._root.style.paddingTop = `${paddingTop}px`; this._root.style.paddingBottom = `${paddingBottom}px`; + } - for (const child of this._childInstances) { - this._removeChild(child); - } - this._childInstances = []; + _renderedFragment(items, childInstanceModifier) { const fragment = document.createDocumentFragment(); - for (const item of renderedItems) { + for (const item of items) { const view = this._childCreator(item); - this._childInstances.push(view); + childInstanceModifier(view); fragment.appendChild(mountView(view, this._mountArgs)); } - this._root.appendChild(fragment); + return fragment; + } + + _renderElementsInRange(range) { + const diff = this._renderRange.diff(range); + const {start, end} = diff.toAdd; + const renderedItems = this._itemsFromList(start, end); + this._adjustPadding(range); + + if (diff.scrollDirection === ScrollDirection.downwards) { + const {start, end} = diff.toRemove; + this._childInstances.splice(0, end - start + 1) + .forEach(child => this._removeChild(child)); + const fragment = this._renderedFragment(renderedItems, view => this._childInstances.push(view)); + this._root.appendChild(fragment); + } + else { + const {start, end} = diff.toRemove; + const normalizedStart = this._renderRange.normalize(start); + this._childInstances.splice(normalizedStart, end - start + 1) + .forEach(child => this._removeChild(child)); + const fragment = this._renderedFragment(renderedItems, view => this._childInstances.unshift(view)); + this._root.insertBefore(fragment, this._root.firstChild); + } + this._renderRange = range; + // for (const child of this._childInstances) { + // this._removeChild(child); + // } + // this._childInstances = []; + + // const fragment = document.createDocumentFragment(); + // for (const item of renderedItems) { + // const view = this._childCreator(item); + // this._childInstances.push(view); + // fragment.appendChild(mountView(view, this._mountArgs)); + // } + // this._root.appendChild(fragment); } mount() { From 168312627df2cc3512f7f986489e44ed8085f1c5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 9 Aug 2021 14:21:42 +0530 Subject: [PATCH 132/174] Render only diff of ranges Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 52 ++++++++++++--------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index 299e511c..ffd834eb 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -83,18 +83,40 @@ class ItemRange { return range.bottomCount < this.bottomCount ? ScrollDirection.downwards : ScrollDirection.upwards; } + /** + * Check if this range intersects with another range + * @param {ItemRange} range The range to check against + * @param {ScrollDirection} scrollDirection + * @returns {Boolean} + */ + intersects(range) { + return !!Math.max(0, Math.min(this.lastIndex, range.lastIndex) - Math.max(this.topCount, range.topCount)); + } + diff(range) { + /** + * Range-1 + * |----------------------| + * Range-2 + * |---------------------| + * <-------><------------><-------> + * bisect-1 intersection bisect-2 + */ const scrollDirection = this.scrollDirectionTo(range); - let toRemove, toAdd; - if (scrollDirection === ScrollDirection.downwards) { - toRemove = { start: this.topCount, end: range.topCount - 1 }; - toAdd = { start: this.lastIndex, end: range.lastIndex }; + if (!this.intersects(range)) { + // There is no intersection between the ranges; which can happen if you scroll really fast + // In this case, we need to do full render of the new range + const toRemove = { start: this.topCount, end: this.lastIndex }; + const toAdd = { start: range.topCount, end: range.lastIndex }; + return {toRemove, toAdd, scrollDirection}; } - else { - toRemove = { start: range.lastIndex, end: this.lastIndex }; - toAdd = { start: range.topCount, end: this.topCount - 1 }; - } - return { toRemove, toAdd, scrollDirection }; + const bisection1 = {start: Math.min(this.topCount, range.topCount), end: Math.max(this.topCount, range.topCount) - 1}; + const bisection2 = {start: Math.min(this.lastIndex, range.lastIndex), end: Math.max(this.lastIndex, range.lastIndex)}; + // When scrolling down, bisection1 needs to be removed and bisection2 needs to be added + // When scrolling up, vice versa + const toRemove = scrollDirection === ScrollDirection.downwards ? bisection1 : bisection2; + const toAdd = scrollDirection === ScrollDirection.downwards ? bisection2 : bisection1; + return {toAdd, toRemove, scrollDirection}; } } @@ -226,18 +248,6 @@ export class LazyListView extends ListView { this._root.insertBefore(fragment, this._root.firstChild); } this._renderRange = range; - // for (const child of this._childInstances) { - // this._removeChild(child); - // } - // this._childInstances = []; - - // const fragment = document.createDocumentFragment(); - // for (const item of renderedItems) { - // const view = this._childCreator(item); - // this._childInstances.push(view); - // fragment.appendChild(mountView(view, this._mountArgs)); - // } - // this._root.appendChild(fragment); } mount() { From a02b6b68d3a81de9c5f06382a161cb3042bb34cf Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 9 Aug 2021 14:35:10 +0530 Subject: [PATCH 133/174] Move common code from if-else Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index ffd834eb..f0de677c 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -173,7 +173,7 @@ export class LazyListView extends ListView { this._renderRange = renderRange; const { topCount, renderCount } = this._renderRange; - const renderedItems = this._itemsFromList(topCount, topCount + renderCount); + const renderedItems = this._itemsFromList({ start: topCount, end: topCount + renderCount}); this._adjustPadding(renderRange); this._childInstances = []; const fragment = document.createDocumentFragment(); @@ -185,7 +185,7 @@ export class LazyListView extends ListView { this._root.appendChild(fragment); } - _itemsFromList(start, end) { + _itemsFromList({start, end}) { const array = []; let i = 0; for (const item of this._list) { @@ -228,22 +228,18 @@ export class LazyListView extends ListView { _renderElementsInRange(range) { const diff = this._renderRange.diff(range); - const {start, end} = diff.toAdd; - const renderedItems = this._itemsFromList(start, end); + const renderedItems = this._itemsFromList(diff.toAdd); this._adjustPadding(range); + const {start, end} = diff.toRemove; + const normalizedStart = this._renderRange.normalize(start); + this._childInstances.splice(normalizedStart, end - start + 1).forEach(child => this._removeChild(child)); + if (diff.scrollDirection === ScrollDirection.downwards) { - const {start, end} = diff.toRemove; - this._childInstances.splice(0, end - start + 1) - .forEach(child => this._removeChild(child)); const fragment = this._renderedFragment(renderedItems, view => this._childInstances.push(view)); this._root.appendChild(fragment); } else { - const {start, end} = diff.toRemove; - const normalizedStart = this._renderRange.normalize(start); - this._childInstances.splice(normalizedStart, end - start + 1) - .forEach(child => this._removeChild(child)); const fragment = this._renderedFragment(renderedItems, view => this._childInstances.unshift(view)); this._root.insertBefore(fragment, this._root.firstChild); } From 587dd3848e6517fb04c9eadcb091aaea3e275f53 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 9 Aug 2021 15:04:17 +0530 Subject: [PATCH 134/174] Use existing render function for initial render Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index f0de677c..aa38a4fb 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -168,21 +168,10 @@ export class LazyListView extends ListView { this._height = this._parent.clientHeight; if (this._height === 0) { console.error("LazyListView could not calculate parent height."); } - const range = this._getVisibleRange(); - const renderRange = range.expand(this._overflowItems); - this._renderRange = renderRange; - - const { topCount, renderCount } = this._renderRange; - const renderedItems = this._itemsFromList({ start: topCount, end: topCount + renderCount}); - this._adjustPadding(renderRange); - this._childInstances = []; - const fragment = document.createDocumentFragment(); - for (const item of renderedItems) { - const view = this._childCreator(item); - this._childInstances.push(view); - fragment.appendChild(mountView(view, this._mountArgs)); - } - this._root.appendChild(fragment); + const initialRange = this._getVisibleRange(); + const initialRenderRange = initialRange.expand(this._overflowItems); + this._renderRange = new ItemRange(0, 0, 0); + this._renderElementsInRange(initialRenderRange); } _itemsFromList({start, end}) { From 83ff2dd810066c263d9b07faa66efd7d97cc20f9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 10 Aug 2021 14:37:33 +0530 Subject: [PATCH 135/174] Fix onAdd Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 29 +++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index aa38a4fb..c4a1c6b9 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -43,6 +43,8 @@ class ItemRange { } containsIndex(idx) { + // TODO: Replace by lastIndex + // TODO: Should idx be <= since lastIndex is not rendered? return idx >= this.topCount && idx <= (this.topCount + this.renderCount); } @@ -170,7 +172,7 @@ export class LazyListView extends ListView { if (this._height === 0) { console.error("LazyListView could not calculate parent height."); } const initialRange = this._getVisibleRange(); const initialRenderRange = initialRange.expand(this._overflowItems); - this._renderRange = new ItemRange(0, 0, 0); + this._renderRange = new ItemRange(0, 0, initialRange.bottomCount + 1); this._renderElementsInRange(initialRenderRange); } @@ -219,7 +221,6 @@ export class LazyListView extends ListView { const diff = this._renderRange.diff(range); const renderedItems = this._itemsFromList(diff.toAdd); this._adjustPadding(range); - const {start, end} = diff.toRemove; const normalizedStart = this._renderRange.normalize(start); this._childInstances.splice(normalizedStart, end - start + 1).forEach(child => this._removeChild(child)); @@ -265,9 +266,27 @@ export class LazyListView extends ListView { child.unmount(); } - // If size of the list changes, re-render - onAdd() { - this._renderIfNeeded(true); + onAdd(idx, value) { + const {topCount, renderCount, bottomCount} = this._renderRange; + if (this._renderRange.containsIndex(idx)) { + const normalizedIdx = this._renderRange.normalize(idx); + if (bottomCount === 0) { + // We're completely scrolled; so the extra element needs to be removed from top + this._removeChild(this._childInstances.shift()); + this._renderRange = new ItemRange(topCount + 1, renderCount, bottomCount); + } + else { + // Remove the last element, render the new element + this._removeChild(this._childInstances.pop()); + this._renderRange = new ItemRange(topCount, renderCount, bottomCount + 1); + } + super.onAdd(normalizedIdx, value); + } + else { + this._renderRange = idx < topCount ? new ItemRange(topCount + 1, renderCount, bottomCount): + new ItemRange(topCount, renderCount, bottomCount + 1); + } + this._adjustPadding(this._renderRange); } onRemove() { From 1165683f69badc40e4354eeb6729ac796a77d551 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 10 Aug 2021 18:44:00 +0530 Subject: [PATCH 136/174] Fix onRemove Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 25 +++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index c4a1c6b9..d94487ba 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -289,8 +289,29 @@ export class LazyListView extends ListView { this._adjustPadding(this._renderRange); } - onRemove() { - this._renderIfNeeded(true); + onRemove(idx, value) { + const {topCount, renderCount, bottomCount} = this._renderRange; + if (this._renderRange.containsIndex(idx)) { + const normalizedIdx = this._renderRange.normalize(idx); + super.onRemove(normalizedIdx, value); + if (bottomCount === 0) { + const child = this._childCreator(this._itemAtIndex(topCount - 1)); + this._childInstances.unshift(child); + this._root.insertBefore(mountView(child, this._mountArgs), this._root.firstChild); + this._renderRange = new ItemRange(topCount - 1, renderCount, bottomCount); + } + else { + const child = this._childCreator(this._itemAtIndex(this._renderRange.lastIndex - 1)); + this._childInstances.push(child); + this._root.appendChild(mountView(child, this._mountArgs)); + this._renderRange = new ItemRange(topCount, renderCount, bottomCount - 1); + } + } + else { + this._renderRange = idx < topCount ? new ItemRange(topCount - 1, renderCount, bottomCount): + new ItemRange(topCount, renderCount, bottomCount - 1); + } + this._adjustPadding(this._renderRange); } onUpdate(idx, value, params) { From 3ae52ea1ca0c37767d9703ca8bbf49ec9930a21f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 Aug 2021 12:23:26 +0530 Subject: [PATCH 137/174] Fix bug in onAdd and onRemove Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index d94487ba..b0d28c51 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -271,9 +271,15 @@ export class LazyListView extends ListView { if (this._renderRange.containsIndex(idx)) { const normalizedIdx = this._renderRange.normalize(idx); if (bottomCount === 0) { - // We're completely scrolled; so the extra element needs to be removed from top - this._removeChild(this._childInstances.shift()); - this._renderRange = new ItemRange(topCount + 1, renderCount, bottomCount); + /* + If we're at the bottom of the list, we need to render the additional item + without removing another item from the list. + We can't increment topCount because the index topCount is not affected by the + add operation (and any modification will thus break ItemRange.normalize()). + We can't increment bottomCount because there's not enough items left to trigger + a further render. + */ + this._renderRange = new ItemRange(topCount, renderCount + 1, bottomCount); } else { // Remove the last element, render the new element @@ -295,10 +301,8 @@ export class LazyListView extends ListView { const normalizedIdx = this._renderRange.normalize(idx); super.onRemove(normalizedIdx, value); if (bottomCount === 0) { - const child = this._childCreator(this._itemAtIndex(topCount - 1)); - this._childInstances.unshift(child); - this._root.insertBefore(mountView(child, this._mountArgs), this._root.firstChild); - this._renderRange = new ItemRange(topCount - 1, renderCount, bottomCount); + // See onAdd for explanation + this._renderRange = new ItemRange(topCount, renderCount - 1, bottomCount); } else { const child = this._childCreator(this._itemAtIndex(this._renderRange.lastIndex - 1)); From e10b494f0ca859aaa27b53e999b4205490925346 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 Aug 2021 12:30:38 +0530 Subject: [PATCH 138/174] Improve containsIndex Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index b0d28c51..2c070d95 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -43,9 +43,7 @@ class ItemRange { } containsIndex(idx) { - // TODO: Replace by lastIndex - // TODO: Should idx be <= since lastIndex is not rendered? - return idx >= this.topCount && idx <= (this.topCount + this.renderCount); + return idx >= this.topCount && idx < this.lastIndex; } expand(amount) { From da715c70b0ec3b5f53b0db6ff437c3cda358f8b2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 Aug 2021 12:37:07 +0530 Subject: [PATCH 139/174] Remove forceRender Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index 2c070d95..00e9b9d8 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -139,17 +139,12 @@ export class LazyListView extends ListView { return new ItemRange(topCount, renderCount, bottomCount); } - _renderIfNeeded(forceRender = false) { - /* - forceRender only because we don't optimize onAdd/onRemove yet. - Ideally, onAdd/onRemove should only render whatever has changed + update padding + update renderRange - */ + _renderIfNeeded() { const range = this._getVisibleRange(); const intersectRange = range.expand(this._overflowMargin); const renderRange = range.expand(this._overflowItems); // only update render Range if the new range + overflowMargin isn't contained by the old anymore - // or if we are force rendering - if (forceRender || !this._renderRange.contains(intersectRange)) { + if (!this._renderRange.contains(intersectRange)) { console.log("new", renderRange); console.log("current", this._renderRange); console.log("diff", this._renderRange.diff(renderRange)); From aee135a6cdaaad5a3a9a52fdf5da9e8f48f66da6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 Aug 2021 12:45:45 +0530 Subject: [PATCH 140/174] Jsdoc fix Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index 00e9b9d8..f1fc533e 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -87,7 +87,7 @@ class ItemRange { * Check if this range intersects with another range * @param {ItemRange} range The range to check against * @param {ScrollDirection} scrollDirection - * @returns {Boolean} + * @returns {boolean} */ intersects(range) { return !!Math.max(0, Math.min(this.lastIndex, range.lastIndex) - Math.max(this.topCount, range.topCount)); From 5d54285640934b5a3cb8b728de684d2140e622c7 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 Aug 2021 12:52:38 +0530 Subject: [PATCH 141/174] Move ItemRange to separate file Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/ItemRange.js | 116 ++++++++++++++++++++ src/platform/web/ui/general/LazyListView.js | 102 +---------------- 2 files changed, 117 insertions(+), 101 deletions(-) create mode 100644 src/platform/web/ui/general/ItemRange.js diff --git a/src/platform/web/ui/general/ItemRange.js b/src/platform/web/ui/general/ItemRange.js new file mode 100644 index 00000000..ab6b23b0 --- /dev/null +++ b/src/platform/web/ui/general/ItemRange.js @@ -0,0 +1,116 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +export class ScrollDirection { + static get downwards() { return 1; } + static get upwards() { return -1; } +} + +export class ItemRange { + constructor(topCount, renderCount, bottomCount) { + this.topCount = topCount; + this.renderCount = renderCount; + this.bottomCount = bottomCount; + } + + contains(range) { + // don't contain empty ranges + // as it will prevent clearing the list + // once it is scrolled far enough out of view + if (!range.renderCount && this.renderCount) { + return false; + } + return range.topCount >= this.topCount && + (range.topCount + range.renderCount) <= (this.topCount + this.renderCount); + } + + containsIndex(idx) { + return idx >= this.topCount && idx < this.lastIndex; + } + + expand(amount) { + // don't expand ranges that won't render anything + if (this.renderCount === 0) { + return this; + } + + const topGrow = Math.min(amount, this.topCount); + const bottomGrow = Math.min(amount, this.bottomCount); + return new ItemRange( + this.topCount - topGrow, + this.renderCount + topGrow + bottomGrow, + this.bottomCount - bottomGrow, + ); + } + + get lastIndex() { + return this.topCount + this.renderCount; + } + + totalSize() { + return this.topCount + this.renderCount + this.bottomCount; + } + + normalize(idx) { + /* + map index from list to index in rendered range + eg: if the index range of this._list is [0, 200] and we have rendered + elements in range [50, 60] then index 50 in list must map to index 0 + in DOM tree/childInstance array. + */ + return idx - this.topCount; + } + + scrollDirectionTo(range) { + return range.bottomCount < this.bottomCount ? ScrollDirection.downwards : ScrollDirection.upwards; + } + + /** + * Check if this range intersects with another range + * @param {ItemRange} range The range to check against + * @param {ScrollDirection} scrollDirection + * @returns {boolean} + */ + intersects(range) { + return !!Math.max(0, Math.min(this.lastIndex, range.lastIndex) - Math.max(this.topCount, range.topCount)); + } + + diff(range) { + /** + * Range-1 + * |----------------------| + * Range-2 + * |---------------------| + * <-------><------------><-------> + * bisect-1 intersection bisect-2 + */ + const scrollDirection = this.scrollDirectionTo(range); + if (!this.intersects(range)) { + // There is no intersection between the ranges; which can happen if you scroll really fast + // In this case, we need to do full render of the new range + const toRemove = { start: this.topCount, end: this.lastIndex }; + const toAdd = { start: range.topCount, end: range.lastIndex }; + return {toRemove, toAdd, scrollDirection}; + } + const bisection1 = {start: Math.min(this.topCount, range.topCount), end: Math.max(this.topCount, range.topCount) - 1}; + const bisection2 = {start: Math.min(this.lastIndex, range.lastIndex), end: Math.max(this.lastIndex, range.lastIndex)}; + // When scrolling down, bisection1 needs to be removed and bisection2 needs to be added + // When scrolling up, vice versa + const toRemove = scrollDirection === ScrollDirection.downwards ? bisection1 : bisection2; + const toAdd = scrollDirection === ScrollDirection.downwards ? bisection2 : bisection1; + return {toAdd, toRemove, scrollDirection}; + } +} diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index f1fc533e..6864172c 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -18,107 +18,7 @@ import {el} from "./html"; import {mountView} from "./utils"; import {ListView} from "./ListView"; import {insertAt} from "./utils"; - -class ScrollDirection { - static get downwards() { return 1; } - static get upwards() { return -1; } -} - -class ItemRange { - constructor(topCount, renderCount, bottomCount) { - this.topCount = topCount; - this.renderCount = renderCount; - this.bottomCount = bottomCount; - } - - contains(range) { - // don't contain empty ranges - // as it will prevent clearing the list - // once it is scrolled far enough out of view - if (!range.renderCount && this.renderCount) { - return false; - } - return range.topCount >= this.topCount && - (range.topCount + range.renderCount) <= (this.topCount + this.renderCount); - } - - containsIndex(idx) { - return idx >= this.topCount && idx < this.lastIndex; - } - - expand(amount) { - // don't expand ranges that won't render anything - if (this.renderCount === 0) { - return this; - } - - const topGrow = Math.min(amount, this.topCount); - const bottomGrow = Math.min(amount, this.bottomCount); - return new ItemRange( - this.topCount - topGrow, - this.renderCount + topGrow + bottomGrow, - this.bottomCount - bottomGrow, - ); - } - - get lastIndex() { - return this.topCount + this.renderCount; - } - - totalSize() { - return this.topCount + this.renderCount + this.bottomCount; - } - - normalize(idx) { - /* - map index from list to index in rendered range - eg: if the index range of this._list is [0, 200] and we have rendered - elements in range [50, 60] then index 50 in list must map to index 0 - in DOM tree/childInstance array. - */ - return idx - this.topCount; - } - - scrollDirectionTo(range) { - return range.bottomCount < this.bottomCount ? ScrollDirection.downwards : ScrollDirection.upwards; - } - - /** - * Check if this range intersects with another range - * @param {ItemRange} range The range to check against - * @param {ScrollDirection} scrollDirection - * @returns {boolean} - */ - intersects(range) { - return !!Math.max(0, Math.min(this.lastIndex, range.lastIndex) - Math.max(this.topCount, range.topCount)); - } - - diff(range) { - /** - * Range-1 - * |----------------------| - * Range-2 - * |---------------------| - * <-------><------------><-------> - * bisect-1 intersection bisect-2 - */ - const scrollDirection = this.scrollDirectionTo(range); - if (!this.intersects(range)) { - // There is no intersection between the ranges; which can happen if you scroll really fast - // In this case, we need to do full render of the new range - const toRemove = { start: this.topCount, end: this.lastIndex }; - const toAdd = { start: range.topCount, end: range.lastIndex }; - return {toRemove, toAdd, scrollDirection}; - } - const bisection1 = {start: Math.min(this.topCount, range.topCount), end: Math.max(this.topCount, range.topCount) - 1}; - const bisection2 = {start: Math.min(this.lastIndex, range.lastIndex), end: Math.max(this.lastIndex, range.lastIndex)}; - // When scrolling down, bisection1 needs to be removed and bisection2 needs to be added - // When scrolling up, vice versa - const toRemove = scrollDirection === ScrollDirection.downwards ? bisection1 : bisection2; - const toAdd = scrollDirection === ScrollDirection.downwards ? bisection2 : bisection1; - return {toAdd, toRemove, scrollDirection}; - } -} +import {ItemRange, ScrollDirection} from "./ItemRange.js"; export class LazyListView extends ListView { constructor({itemHeight, overflowMargin = 5, overflowItems = 20,...options}, childCreator) { From 33ac34b04e05eced0ffec18cf9f985e7e74d5e20 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 20:48:51 +0530 Subject: [PATCH 142/174] Do not break onListChanged Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/LazyListView.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index 6864172c..c064a2b5 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -162,6 +162,7 @@ export class LazyListView extends ListView { onAdd(idx, value) { const {topCount, renderCount, bottomCount} = this._renderRange; if (this._renderRange.containsIndex(idx)) { + this.onBeforeListChanged(); const normalizedIdx = this._renderRange.normalize(idx); if (bottomCount === 0) { /* @@ -179,7 +180,8 @@ export class LazyListView extends ListView { this._removeChild(this._childInstances.pop()); this._renderRange = new ItemRange(topCount, renderCount, bottomCount + 1); } - super.onAdd(normalizedIdx, value); + super.onAdd(normalizedIdx, value, true); + this.onListChanged(); } else { this._renderRange = idx < topCount ? new ItemRange(topCount + 1, renderCount, bottomCount): @@ -191,8 +193,9 @@ export class LazyListView extends ListView { onRemove(idx, value) { const {topCount, renderCount, bottomCount} = this._renderRange; if (this._renderRange.containsIndex(idx)) { + this.onBeforeListChanged(); const normalizedIdx = this._renderRange.normalize(idx); - super.onRemove(normalizedIdx, value); + super.onRemove(normalizedIdx, value, true); if (bottomCount === 0) { // See onAdd for explanation this._renderRange = new ItemRange(topCount, renderCount - 1, bottomCount); @@ -203,6 +206,7 @@ export class LazyListView extends ListView { this._root.appendChild(mountView(child, this._mountArgs)); this._renderRange = new ItemRange(topCount, renderCount, bottomCount - 1); } + this.onListChanged(); } else { this._renderRange = idx < topCount ? new ItemRange(topCount - 1, renderCount, bottomCount): From bbeb909bdc233a2e874e5855ef69d70493db12d4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 15 Aug 2021 21:07:40 +0530 Subject: [PATCH 143/174] Use createEnum Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/ItemRange.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/platform/web/ui/general/ItemRange.js b/src/platform/web/ui/general/ItemRange.js index ab6b23b0..53627aa4 100644 --- a/src/platform/web/ui/general/ItemRange.js +++ b/src/platform/web/ui/general/ItemRange.js @@ -14,10 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -export class ScrollDirection { - static get downwards() { return 1; } - static get upwards() { return -1; } -} +import {createEnum} from "../../../../utils/enum.js"; + +export const ScrollDirection = createEnum("upwards", "downwards"); export class ItemRange { constructor(topCount, renderCount, bottomCount) { From d625d57aa4ec89dbf4b37a1ea27f3a3c2989db73 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 16 Aug 2021 15:28:57 +0530 Subject: [PATCH 144/174] Fix lastIndex Signed-off-by: RMidhunSuresh --- src/platform/web/ui/general/ItemRange.js | 6 +++--- src/platform/web/ui/general/LazyListView.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/web/ui/general/ItemRange.js b/src/platform/web/ui/general/ItemRange.js index 53627aa4..494f5dcb 100644 --- a/src/platform/web/ui/general/ItemRange.js +++ b/src/platform/web/ui/general/ItemRange.js @@ -37,7 +37,7 @@ export class ItemRange { } containsIndex(idx) { - return idx >= this.topCount && idx < this.lastIndex; + return idx >= this.topCount && idx <= this.lastIndex; } expand(amount) { @@ -56,7 +56,7 @@ export class ItemRange { } get lastIndex() { - return this.topCount + this.renderCount; + return this.topCount + this.renderCount - 1; } totalSize() { @@ -105,7 +105,7 @@ export class ItemRange { return {toRemove, toAdd, scrollDirection}; } const bisection1 = {start: Math.min(this.topCount, range.topCount), end: Math.max(this.topCount, range.topCount) - 1}; - const bisection2 = {start: Math.min(this.lastIndex, range.lastIndex), end: Math.max(this.lastIndex, range.lastIndex)}; + const bisection2 = {start: Math.min(this.lastIndex, range.lastIndex) + 1, end: Math.max(this.lastIndex, range.lastIndex)}; // When scrolling down, bisection1 needs to be removed and bisection2 needs to be added // When scrolling up, vice versa const toRemove = scrollDirection === ScrollDirection.downwards ? bisection1 : bisection2; diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index c064a2b5..3213ffee 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -73,7 +73,7 @@ export class LazyListView extends ListView { const array = []; let i = 0; for (const item of this._list) { - if (i >= start && i < end) { + if (i >= start && i <= end) { array.push(item); } i = i + 1; @@ -201,7 +201,7 @@ export class LazyListView extends ListView { this._renderRange = new ItemRange(topCount, renderCount - 1, bottomCount); } else { - const child = this._childCreator(this._itemAtIndex(this._renderRange.lastIndex - 1)); + const child = this._childCreator(this._itemAtIndex(this._renderRange.lastIndex)); this._childInstances.push(child); this._root.appendChild(mountView(child, this._mountArgs)); this._renderRange = new ItemRange(topCount, renderCount, bottomCount - 1); From 4a64d0ee171b442b3b4f1354b3ef641b2a25cd86 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 19 Nov 2021 22:49:46 +0100 Subject: [PATCH 145/174] WIP --- src/platform/web/ui/general/ItemRange.js | 115 ---------- src/platform/web/ui/general/ItemRange.ts | 210 ++++++++++++++++++ src/platform/web/ui/general/LazyListView.ts | 200 +++++++++++++++++ src/platform/web/ui/general/ListView.ts | 53 +++-- .../{LazyListView.js => foo-LazyListView.js} | 0 src/platform/web/ui/general/utils.ts | 4 + .../ui/session/rightpanel/MemberListView.js | 2 +- 7 files changed, 450 insertions(+), 134 deletions(-) delete mode 100644 src/platform/web/ui/general/ItemRange.js create mode 100644 src/platform/web/ui/general/ItemRange.ts create mode 100644 src/platform/web/ui/general/LazyListView.ts rename src/platform/web/ui/general/{LazyListView.js => foo-LazyListView.js} (100%) diff --git a/src/platform/web/ui/general/ItemRange.js b/src/platform/web/ui/general/ItemRange.js deleted file mode 100644 index 494f5dcb..00000000 --- a/src/platform/web/ui/general/ItemRange.js +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2021 The Matrix.org Foundation C.I.C. - -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 {createEnum} from "../../../../utils/enum.js"; - -export const ScrollDirection = createEnum("upwards", "downwards"); - -export class ItemRange { - constructor(topCount, renderCount, bottomCount) { - this.topCount = topCount; - this.renderCount = renderCount; - this.bottomCount = bottomCount; - } - - contains(range) { - // don't contain empty ranges - // as it will prevent clearing the list - // once it is scrolled far enough out of view - if (!range.renderCount && this.renderCount) { - return false; - } - return range.topCount >= this.topCount && - (range.topCount + range.renderCount) <= (this.topCount + this.renderCount); - } - - containsIndex(idx) { - return idx >= this.topCount && idx <= this.lastIndex; - } - - expand(amount) { - // don't expand ranges that won't render anything - if (this.renderCount === 0) { - return this; - } - - const topGrow = Math.min(amount, this.topCount); - const bottomGrow = Math.min(amount, this.bottomCount); - return new ItemRange( - this.topCount - topGrow, - this.renderCount + topGrow + bottomGrow, - this.bottomCount - bottomGrow, - ); - } - - get lastIndex() { - return this.topCount + this.renderCount - 1; - } - - totalSize() { - return this.topCount + this.renderCount + this.bottomCount; - } - - normalize(idx) { - /* - map index from list to index in rendered range - eg: if the index range of this._list is [0, 200] and we have rendered - elements in range [50, 60] then index 50 in list must map to index 0 - in DOM tree/childInstance array. - */ - return idx - this.topCount; - } - - scrollDirectionTo(range) { - return range.bottomCount < this.bottomCount ? ScrollDirection.downwards : ScrollDirection.upwards; - } - - /** - * Check if this range intersects with another range - * @param {ItemRange} range The range to check against - * @param {ScrollDirection} scrollDirection - * @returns {boolean} - */ - intersects(range) { - return !!Math.max(0, Math.min(this.lastIndex, range.lastIndex) - Math.max(this.topCount, range.topCount)); - } - - diff(range) { - /** - * Range-1 - * |----------------------| - * Range-2 - * |---------------------| - * <-------><------------><-------> - * bisect-1 intersection bisect-2 - */ - const scrollDirection = this.scrollDirectionTo(range); - if (!this.intersects(range)) { - // There is no intersection between the ranges; which can happen if you scroll really fast - // In this case, we need to do full render of the new range - const toRemove = { start: this.topCount, end: this.lastIndex }; - const toAdd = { start: range.topCount, end: range.lastIndex }; - return {toRemove, toAdd, scrollDirection}; - } - const bisection1 = {start: Math.min(this.topCount, range.topCount), end: Math.max(this.topCount, range.topCount) - 1}; - const bisection2 = {start: Math.min(this.lastIndex, range.lastIndex) + 1, end: Math.max(this.lastIndex, range.lastIndex)}; - // When scrolling down, bisection1 needs to be removed and bisection2 needs to be added - // When scrolling up, vice versa - const toRemove = scrollDirection === ScrollDirection.downwards ? bisection1 : bisection2; - const toAdd = scrollDirection === ScrollDirection.downwards ? bisection2 : bisection1; - return {toAdd, toRemove, scrollDirection}; - } -} diff --git a/src/platform/web/ui/general/ItemRange.ts b/src/platform/web/ui/general/ItemRange.ts new file mode 100644 index 00000000..66db6078 --- /dev/null +++ b/src/platform/web/ui/general/ItemRange.ts @@ -0,0 +1,210 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +// start is included in the range, +// end is excluded, +// so [2, 2[ means an empty range +class Range { + constructor( + public readonly start: number, + public readonly end: number + ) {} + + get length() { + return this.end - this.start; + } + + contains(range: Range): boolean { + return range.start >= this.start && range.end <= this.end; + } + + containsIndex(idx: number): boolean { + return idx >= this.start && idx < this.end; + } + + intersects(range: Range): boolean { + return range.start < this.end && this.start < range.end; + } + + forEach(callback: ((i: number) => void)) { + for (let i = this.start; i < this.end; i += 1) { + callback(i); + } + } + + forEachInIterator(it: IterableIterator, callback: ((T, i: number) => void)) { + let i = 0; + for (i = 0; i < this.start; i += 1) { + it.next(); + } + for (i = 0; i < this.length; i += 1) { + const result = it.next(); + if (result.done) { + break; + } else { + callback(result.value, this.start + i); + } + } + } + + [Symbol.iterator](): Iterator { + return new RangeIterator(this); + } +} + +class RangeIterator implements Iterator { + private idx: number; + constructor(private readonly range: Range) { + this.idx = range.start - 1; + } + + next(): IteratorResult { + if (this.idx < (this.range.end - 1)) { + this.idx += 1; + return {value: this.idx, done: false}; + } else { + return {value: undefined, done: true}; + } + } +} + +export function tests() { + return { + "length": assert => { + const a = new Range(2, 5); + assert.equal(a.length, 3); + }, + "iterator": assert => { + assert.deepEqual(Array.from(new Range(2, 5)), [2, 3, 4]); + }, + "containsIndex": assert => { + const a = new Range(2, 5); + assert.equal(a.containsIndex(0), false); + assert.equal(a.containsIndex(1), false); + assert.equal(a.containsIndex(2), true); + assert.equal(a.containsIndex(3), true); + assert.equal(a.containsIndex(4), true); + assert.equal(a.containsIndex(5), false); + assert.equal(a.containsIndex(6), false); + }, + "intersects returns false for touching ranges": assert => { + const a = new Range(2, 5); + const b = new Range(5, 10); + assert.equal(a.intersects(b), false); + assert.equal(b.intersects(a), false); + }, + "intersects returns false": assert => { + const a = new Range(2, 5); + const b = new Range(50, 100); + assert.equal(a.intersects(b), false); + assert.equal(b.intersects(a), false); + }, + "intersects returns true for 1 overlapping item": assert => { + const a = new Range(2, 5); + const b = new Range(4, 10); + assert.equal(a.intersects(b), true); + assert.equal(b.intersects(a), true); + }, + "contains beyond left edge": assert => { + const a = new Range(2, 5); + const b = new Range(1, 3); + assert.equal(a.contains(b), false); + }, + "contains at left edge": assert => { + const a = new Range(2, 5); + const b = new Range(2, 3); + assert.equal(a.contains(b), true); + }, + "contains between edges": assert => { + const a = new Range(2, 5); + const b = new Range(3, 4); + assert.equal(a.contains(b), true); + }, + "contains at right edge": assert => { + const a = new Range(2, 5); + const b = new Range(3, 5); + assert.equal(a.contains(b), true); + }, + "contains beyond right edge": assert => { + const a = new Range(2, 5); + const b = new Range(4, 6); + assert.equal(a.contains(b), false); + }, + "contains for non-intersecting ranges": assert => { + const a = new Range(2, 5); + const b = new Range(5, 6); + assert.equal(a.contains(b), false); + }, + "forEachInIterator with more values available": assert => { + const callbackValues: {v: string, i: number}[] = []; + const values = ["a", "b", "c", "d", "e", "f"]; + const it = values[Symbol.iterator](); + new Range(2, 5).forEachInIterator(it, (v, i) => callbackValues.push({v, i})); + assert.deepEqual(callbackValues, [ + {v: "c", i: 2}, + {v: "d", i: 3}, + {v: "e", i: 4}, + ]); + }, + "forEachInIterator with fewer values available": assert => { + const callbackValues: {v: string, i: number}[] = []; + const values = ["a", "b", "c"]; + const it = values[Symbol.iterator](); + new Range(2, 5).forEachInIterator(it, (v, i) => callbackValues.push({v, i})); + assert.deepEqual(callbackValues, [ + {v: "c", i: 2}, + ]); + }, + }; +} + +export class ItemRange extends Range { + constructor( + start: number, + end: number, + public readonly totalLength: number + ) { + super(start, end); + } + + + expand(amount: number): ItemRange { + // don't expand ranges that won't render anything + if (this.length === 0) { + return this; + } + + const topGrow = Math.min(amount, this.start); + const bottomGrow = Math.min(amount, this.totalLength - this.end); + return new ItemRange( + this.start - topGrow, + this.end + topGrow + bottomGrow, + this.totalLength, + ); + } + + static fromViewport(listLength: number, itemHeight: number, listHeight: number, scrollTop: number) { + const topCount = Math.min(Math.max(0, Math.floor(scrollTop / itemHeight)), listLength); + const itemsAfterTop = listLength - topCount; + const visibleItems = listHeight !== 0 ? Math.ceil(listHeight / itemHeight) : 0; + const renderCount = Math.min(visibleItems, itemsAfterTop); + return new ItemRange(topCount, topCount + renderCount, listLength); + } + + missingFrom() { + + } +} diff --git a/src/platform/web/ui/general/LazyListView.ts b/src/platform/web/ui/general/LazyListView.ts new file mode 100644 index 00000000..65c53c2d --- /dev/null +++ b/src/platform/web/ui/general/LazyListView.ts @@ -0,0 +1,200 @@ +/* +Copyright 2020 Bruno Windels + +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 {tag} from "./html"; +import {removeChildren, mountView} from "./utils"; +import {ItemRange} from "./ItemRange"; +import {ListView, IOptions as IParentOptions} from "./ListView"; +import {IView} from "./types"; + +export interface IOptions extends IParentOptions { + itemHeight: number; + overflowMargin?: number; + overflowItems?: number; +} + +export class LazyListView extends ListView { + private renderRange?: ItemRange; + private height?: number; + private itemHeight: number; + private overflowItems: number; + private scrollContainer?: Element; + + constructor( + {itemHeight, overflowMargin = 5, overflowItems = 20,...options}: IOptions, + childCreator: (value: T) => V + ) { + super(options, childCreator); + this.itemHeight = itemHeight; + this.overflowItems = overflowItems; + // TODO: this.overflowMargin = overflowMargin; + } + + handleEvent(e: Event) { + if (e.type === "scroll") { + this.handleScroll(); + } else { + super.handleEvent(e); + } + } + + handleScroll() { + const visibleRange = this._getVisibleRange(); + // don't contain empty ranges + // as it will prevent clearing the list + // once it is scrolled far enough out of view + if (visibleRange.length !== 0 && !this.renderRange!.contains(visibleRange)) { + const prevRenderRange = this.renderRange!; + this.renderRange = visibleRange.expand(this.overflowItems); + this.renderUpdate(prevRenderRange, this.renderRange); + } + } + + override async loadList() { + /* + Wait two frames for the return from mount() to be inserted into DOM. + This should be enough, but if this gives us trouble we can always use + MutationObserver. + */ + await new Promise(r => requestAnimationFrame(r)); + await new Promise(r => requestAnimationFrame(r)); + + if (!this._list) { + return; + } + const visibleRange = this._getVisibleRange(); + this.renderRange = visibleRange.expand(this.overflowItems); + this._childInstances = []; + this._subscription = this._list.subscribe(this); + this.reRenderFullRange(this.renderRange); + } + + private _getVisibleRange() { + const {clientHeight, scrollTop} = this.root()!; + if (clientHeight === 0) { + throw new Error("LazyListView height is 0"); + } + return ItemRange.fromViewport(this._list.length, this.itemHeight, clientHeight, scrollTop); + } + + private reRenderFullRange(range: ItemRange) { + removeChildren(this._listElement!); + const fragment = document.createDocumentFragment(); + const it = this._list[Symbol.iterator](); + this._childInstances!.length = 0; + range.forEachInIterator(it, item => { + const child = this._childCreator(item); + this._childInstances!.push(child); + fragment.appendChild(mountView(child, this._mountArgs)); + }); + this._listElement!.appendChild(fragment); + this.adjustPadding(range); + } + + private renderUpdate(prevRange: ItemRange, newRange: ItemRange) { + if (newRange.intersects(prevRange)) { + for (const idxInList of prevRange) { + // TODO: we need to make sure we keep childInstances in order so the indices lign up. + // Perhaps we should join both ranges and see in which range it appears and either add or remove? + if (!newRange.containsIndex(idxInList)) { + const localIdx = idxInList - prevRange.start; + this.removeChild(localIdx); + } + } + const addedRange = newRange.missingFrom(prevRange); + addedRange.forEachInIterator(this._list[Symbol.iterator](), (item, idxInList) => { + const localIdx = idxInList - newRange.start; + this.addChild(localIdx, item); + }); + this.adjustPadding(newRange); + } else { + this.reRenderFullRange(newRange); + } + } + + private adjustPadding(range: ItemRange) { + const paddingTop = range.start * this.itemHeight; + const paddingBottom = (range.totalLength - range.end) * this.itemHeight; + const style = this.scrollContainer!.style; + style.paddingTop = `${paddingTop}px`; + style.paddingBottom = `${paddingBottom}px`; + } + + mount() { + const listElement = super.mount(); + this.scrollContainer = tag.div({className: "LazyListParent"}, listElement); + /* + Hooking to scroll events can be expensive. + Do we need to do more (like event throttling)? + */ + this.scrollContainer.addEventListener("scroll", this); + return this.scrollContainer; + } + + unmount() { + this.root()!.removeEventListener("scroll", this); + this.scrollContainer = undefined; + super.unmount(); + } + + root(): Element | undefined { + return this.scrollContainer; + } + + private get _listElement(): Element | undefined { + return super.root(); + } + + onAdd(idx: number, value: T) { + // TODO: update totalLength in renderRange + const result = this.renderRange!.queryAdd(idx); + if (result.addIdx !== -1) { + this.addChild(result.addIdx, value); + } + if (result.removeIdx !== -1) { + this.removeChild(result.removeIdx); + } + } + + onRemove(idx: number, value: T) { + // TODO: update totalLength in renderRange + const result = this.renderRange!.queryRemove(idx); + if (result.removeIdx !== -1) { + this.removeChild(result.removeIdx); + } + if (result.addIdx !== -1) { + this.addChild(result.addIdx, value); + } + } + + onMove(fromIdx: number, toIdx: number, value: T) { + const result = this.renderRange!.queryMove(fromIdx, toIdx); + if (result.moveFromIdx !== -1 && result.moveToIdx !== -1) { + this.moveChild(result.moveFromIdx, result.moveToIdx); + } else if (result.removeIdx !== -1) { + this.removeChild(result.removeIdx); + } else if (result.addIdx !== -1) { + this.addChild(result.addIdx, value); + } + } + + onUpdate(i: number, value: T, params: any) { + const updateIdx = this.renderRange!.queryUpdate(i); + if (updateIdx !== -1) { + this.updateChild(updateIdx, value, params); + } + } +} diff --git a/src/platform/web/ui/general/ListView.ts b/src/platform/web/ui/general/ListView.ts index f76bd961..9d639098 100644 --- a/src/platform/web/ui/general/ListView.ts +++ b/src/platform/web/ui/general/ListView.ts @@ -17,10 +17,10 @@ limitations under the License. import {el} from "./html"; import {mountView, insertAt} from "./utils"; import {SubscriptionHandle} from "../../../../observable/BaseObservable"; -import {BaseObservableList as ObservableList} from "../../../../observable/list/BaseObservableList"; +import {BaseObservableList as ObservableList, IListObserver} from "../../../../observable/list/BaseObservableList"; import {IView, IMountArgs} from "./types"; -interface IOptions { +export interface IOptions { list: ObservableList, onItemClick?: (childView: V, evt: UIEvent) => void, className?: string, @@ -28,17 +28,17 @@ interface IOptions { parentProvidesUpdates?: boolean } -export class ListView implements IView { +export class ListView implements IView, IListObserver { private _onItemClick?: (childView: V, evt: UIEvent) => void; - private _list: ObservableList; private _className?: string; private _tagName: string; private _root?: Element; - private _subscription?: SubscriptionHandle; - private _childCreator: (value: T) => V; - private _childInstances?: V[]; - private _mountArgs: IMountArgs; + protected _subscription?: SubscriptionHandle; + protected _childCreator: (value: T) => V; + protected _mountArgs: IMountArgs; + protected _list: ObservableList; + protected _childInstances?: V[]; constructor( {list, onItemClick, className, tagName = "ul", parentProvidesUpdates = true}: IOptions, @@ -145,31 +145,48 @@ export class ListView implements IView { } onAdd(idx: number, value: T) { - const child = this._childCreator(value); - this._childInstances!.splice(idx, 0, child); - insertAt(this._root!, idx, mountView(child, this._mountArgs)); + this.addChild(idx, value); } onRemove(idx: number, value: T) { - const [child] = this._childInstances!.splice(idx, 1); + this.removeChild(idx); + } + + onMove(fromIdx: number, toIdx: number, value: T) { + this.moveChild(fromIdx, toIdx); + } + + onUpdate(i: number, value: T, params: any) { + this.updateChild(i, value, params); + } + + protected addChild(childIdx: number, value: T) { + const child = this._childCreator(value); + this._childInstances!.splice(childIdx, 0, child); + insertAt(this._root!, childIdx, mountView(child, this._mountArgs)); + } + + protected removeChild(childIdx: number) { + const [child] = this._childInstances!.splice(childIdx, 1); child.root()!.remove(); child.unmount(); } - onMove(fromIdx: number, toIdx: number, value: T) { - const [child] = this._childInstances!.splice(fromIdx, 1); - this._childInstances!.splice(toIdx, 0, child); + protected moveChild(fromChildIdx: number, toChildIdx: number) { + const [child] = this._childInstances!.splice(fromChildIdx, 1); + this._childInstances!.splice(toChildIdx, 0, child); child.root()!.remove(); - insertAt(this._root!, toIdx, child.root()! as Element); + insertAt(this._root!, toChildIdx, child.root()! as Element); } - onUpdate(i: number, value: T, params: any) { + protected updateChild(childIdx: number, value: T, params: any) { if (this._childInstances) { - const instance = this._childInstances![i]; + const instance = this._childInstances![childIdx]; instance && instance.update(value, params); } } + // TODO: is this the list or view index? protected recreateItem(index: number, value: T) { if (this._childInstances) { const child = this._childCreator(value); diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/foo-LazyListView.js similarity index 100% rename from src/platform/web/ui/general/LazyListView.js rename to src/platform/web/ui/general/foo-LazyListView.js diff --git a/src/platform/web/ui/general/utils.ts b/src/platform/web/ui/general/utils.ts index 7eb1d7f9..f8d407e9 100644 --- a/src/platform/web/ui/general/utils.ts +++ b/src/platform/web/ui/general/utils.ts @@ -50,3 +50,7 @@ export function insertAt(parentNode: Element, idx: number, childNode: Node): voi parentNode.insertBefore(childNode, nextDomNode); } } + +export function removeChildren(parentNode: Element): void { + parentNode.innerHTML = ''; +} diff --git a/src/platform/web/ui/session/rightpanel/MemberListView.js b/src/platform/web/ui/session/rightpanel/MemberListView.js index 6c3d252e..6096f919 100644 --- a/src/platform/web/ui/session/rightpanel/MemberListView.js +++ b/src/platform/web/ui/session/rightpanel/MemberListView.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {LazyListView} from "../../general/LazyListView.js"; +import {LazyListView} from "../../general/LazyListView"; import {MemberTileView} from "./MemberTileView.js"; export class MemberListView extends LazyListView{ From cf9f43ab9ebea291af1d6478c44b6f6d30293f74 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 22 Nov 2021 20:35:57 +0100 Subject: [PATCH 146/174] WIP2 --- src/observable/list/ObservableArray.js | 8 + src/platform/web/ui/general/LazyListView.ts | 92 ++-- src/platform/web/ui/general/ListRange.ts | 440 ++++++++++++++++++ .../web/ui/general/{ItemRange.ts => Range.ts} | 105 +++-- 4 files changed, 555 insertions(+), 90 deletions(-) create mode 100644 src/platform/web/ui/general/ListRange.ts rename src/platform/web/ui/general/{ItemRange.ts => Range.ts} (78%) diff --git a/src/observable/list/ObservableArray.js b/src/observable/list/ObservableArray.js index 5d9f5c12..053e7320 100644 --- a/src/observable/list/ObservableArray.js +++ b/src/observable/list/ObservableArray.js @@ -44,6 +44,14 @@ export class ObservableArray extends BaseObservableList { this.emitAdd(idx, item); } + move(fromIdx, toIdx) { + if (fromIdx < this._items.length && toIdx < this._items.length) { + const item = this._items.splice(fromIdx, 1); + this._items.splice(toIdx, 0, item); + this.emitMove(fromIdx, toIdx, item); + } + } + update(idx, item, params = null) { if (idx < this._items.length) { this._items[idx] = item; diff --git a/src/platform/web/ui/general/LazyListView.ts b/src/platform/web/ui/general/LazyListView.ts index 65c53c2d..f8aa80cc 100644 --- a/src/platform/web/ui/general/LazyListView.ts +++ b/src/platform/web/ui/general/LazyListView.ts @@ -16,7 +16,7 @@ limitations under the License. import {tag} from "./html"; import {removeChildren, mountView} from "./utils"; -import {ItemRange} from "./ItemRange"; +import {ListRange, ResultType, AddRemoveResult} from "./ListRange"; import {ListView, IOptions as IParentOptions} from "./ListView"; import {IView} from "./types"; @@ -27,11 +27,11 @@ export interface IOptions extends IParentOptions { } export class LazyListView extends ListView { - private renderRange?: ItemRange; + private renderRange?: ListRange; private height?: number; private itemHeight: number; private overflowItems: number; - private scrollContainer?: Element; + private scrollContainer?: HTMLElement; constructor( {itemHeight, overflowMargin = 5, overflowItems = 20,...options}: IOptions, @@ -87,10 +87,10 @@ export class LazyListView extends ListView { if (clientHeight === 0) { throw new Error("LazyListView height is 0"); } - return ItemRange.fromViewport(this._list.length, this.itemHeight, clientHeight, scrollTop); + return ListRange.fromViewport(this._list.length, this.itemHeight, clientHeight, scrollTop); } - private reRenderFullRange(range: ItemRange) { + private reRenderFullRange(range: ListRange) { removeChildren(this._listElement!); const fragment = document.createDocumentFragment(); const it = this._list[Symbol.iterator](); @@ -104,20 +104,22 @@ export class LazyListView extends ListView { this.adjustPadding(range); } - private renderUpdate(prevRange: ItemRange, newRange: ItemRange) { + private renderUpdate(prevRange: ListRange, newRange: ListRange) { if (newRange.intersects(prevRange)) { - for (const idxInList of prevRange) { - // TODO: we need to make sure we keep childInstances in order so the indices lign up. - // Perhaps we should join both ranges and see in which range it appears and either add or remove? + // remove children in reverse order so child index isn't affected by previous removals + for (const idxInList of prevRange.reverseIterable()) { if (!newRange.containsIndex(idxInList)) { const localIdx = idxInList - prevRange.start; this.removeChild(localIdx); } } - const addedRange = newRange.missingFrom(prevRange); - addedRange.forEachInIterator(this._list[Symbol.iterator](), (item, idxInList) => { - const localIdx = idxInList - newRange.start; - this.addChild(localIdx, item); + // use forEachInIterator instead of for loop as we need to advance + // the list iterator to the start of the range first + newRange.forEachInIterator(this._list[Symbol.iterator](), (item, idxInList) => { + if (!prevRange.containsIndex(idxInList)) { + const localIdx = idxInList - newRange.start; + this.addChild(localIdx, item); + } }); this.adjustPadding(newRange); } else { @@ -125,7 +127,7 @@ export class LazyListView extends ListView { } } - private adjustPadding(range: ItemRange) { + private adjustPadding(range: ListRange) { const paddingTop = range.start * this.itemHeight; const paddingBottom = (range.totalLength - range.end) * this.itemHeight; const style = this.scrollContainer!.style; @@ -135,11 +137,7 @@ export class LazyListView extends ListView { mount() { const listElement = super.mount(); - this.scrollContainer = tag.div({className: "LazyListParent"}, listElement); - /* - Hooking to scroll events can be expensive. - Do we need to do more (like event throttling)? - */ + this.scrollContainer = tag.div({className: "LazyListParent"}, listElement) as HTMLElement; this.scrollContainer.addEventListener("scroll", this); return this.scrollContainer; } @@ -159,42 +157,46 @@ export class LazyListView extends ListView { } onAdd(idx: number, value: T) { - // TODO: update totalLength in renderRange - const result = this.renderRange!.queryAdd(idx); - if (result.addIdx !== -1) { - this.addChild(result.addIdx, value); - } - if (result.removeIdx !== -1) { - this.removeChild(result.removeIdx); - } + const result = this.renderRange!.queryAdd(idx, value, this._list); + this.applyRemoveAddResult(result); } onRemove(idx: number, value: T) { - // TODO: update totalLength in renderRange - const result = this.renderRange!.queryRemove(idx); - if (result.removeIdx !== -1) { - this.removeChild(result.removeIdx); - } - if (result.addIdx !== -1) { - this.addChild(result.addIdx, value); - } + const result = this.renderRange!.queryRemove(idx, this._list); + this.applyRemoveAddResult(result); } onMove(fromIdx: number, toIdx: number, value: T) { - const result = this.renderRange!.queryMove(fromIdx, toIdx); - if (result.moveFromIdx !== -1 && result.moveToIdx !== -1) { - this.moveChild(result.moveFromIdx, result.moveToIdx); - } else if (result.removeIdx !== -1) { - this.removeChild(result.removeIdx); - } else if (result.addIdx !== -1) { - this.addChild(result.addIdx, value); + const result = this.renderRange!.queryMove(fromIdx, toIdx, value, this._list); + if (result) { + if (result.type === ResultType.Move) { + this.moveChild( + this.renderRange!.toLocalIndex(result.fromIdx), + this.renderRange!.toLocalIndex(result.toIdx) + ); + } else { + this.applyRemoveAddResult(result); + } } } onUpdate(i: number, value: T, params: any) { - const updateIdx = this.renderRange!.queryUpdate(i); - if (updateIdx !== -1) { - this.updateChild(updateIdx, value, params); + if (this.renderRange!.containsIndex(i)) { + this.updateChild(this.renderRange!.toLocalIndex(i), value, params); + } + } + + private applyRemoveAddResult(result: AddRemoveResult) { + // order is important here, the new range can have a different start + if (result.type === ResultType.Remove || result.type === ResultType.RemoveAndAdd) { + this.removeChild(this.renderRange!.toLocalIndex(result.removeIdx)); + } + if (result.newRange) { + this.renderRange = result.newRange; + this.adjustPadding(this.renderRange) + } + if (result.type === ResultType.Add || result.type === ResultType.RemoveAndAdd) { + this.addChild(this.renderRange!.toLocalIndex(result.addIdx), result.value); } } } diff --git a/src/platform/web/ui/general/ListRange.ts b/src/platform/web/ui/general/ListRange.ts new file mode 100644 index 00000000..7ccd5726 --- /dev/null +++ b/src/platform/web/ui/general/ListRange.ts @@ -0,0 +1,440 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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 {Range, RangeZone} from "./Range"; + +function skipOnIterator(it: Iterator, pos: number): boolean { + let i = 0; + while (i < pos) { + i += 1; + if(it.next().done) { + return false; + } + } + return true; +} + +function getIteratorValueAtIdx(it: Iterator, idx: number): undefined | T { + if (skipOnIterator(it, idx)) { + const result = it.next(); + if (!result.done) { + return result.value; + } + } + return undefined; +} + +export enum ResultType { + Move, + Add, + Remove, + RemoveAndAdd, + UpdateRange +} + +export interface MoveResult { + type: ResultType.Move; + fromIdx: number; + toIdx: number +} + +interface AddResult { + type: ResultType.Add; + newRange?: ListRange; + /** the list index of an item to add */ + addIdx: number; + /** the value to add at addIdx */ + value: T +} + +interface RemoveResult { + type: ResultType.Remove; + newRange?: ListRange; + /** the list index of an item to remove, before the add or remove event has been taken into account */ + removeIdx: number; +} + +interface RemoveAndAddResult { + type: ResultType.RemoveAndAdd; + newRange?: ListRange; + /** the list index of an item to remove, before the add or remove event has been taken into account */ + removeIdx: number; + /** the list index of an item to add */ + addIdx: number; + /** the value to add at addIdx */ + value: T; +} + +interface UpdateRangeResult { + type: ResultType.UpdateRange; + newRange?: ListRange; +} + +export type AddRemoveResult = AddResult | RemoveResult | RemoveAndAddResult | UpdateRangeResult; + +export class ListRange extends Range { + constructor( + start: number, + end: number, + private _totalLength: number, + private _viewportItemCount: number = end - start + ) { + super(start, end); + } + + expand(amount: number): ListRange { + // don't expand ranges that won't render anything + if (this.length === 0) { + return this; + } + const newStart = Math.max(0, this.start - amount); + const newEnd = Math.min(this.totalLength, this.end + amount); + return new ListRange( + newStart, + newEnd, + this.totalLength, + this._viewportItemCount + ); + } + + get totalLength(): number { + return this._totalLength; + } + + get viewportItemCount(): number { + return this._viewportItemCount; + } + + static fromViewport(listLength: number, itemHeight: number, listHeight: number, scrollTop: number) { + const topCount = Math.min(Math.max(0, Math.floor(scrollTop / itemHeight)), listLength); + const itemsAfterTop = listLength - topCount; + const viewportItemCount = listHeight !== 0 ? Math.ceil(listHeight / itemHeight) : 0; + const renderCount = Math.min(viewportItemCount, itemsAfterTop); + return new ListRange(topCount, topCount + renderCount, listLength, viewportItemCount); + } + + queryAdd(idx: number, value: T, list: Iterable): AddRemoveResult { + const maxAddIdx = this.viewportItemCount > this.length ? this.end : this.end - 1; + if (idx <= maxAddIdx) { + // use maxAddIdx to allow to grow the range by one at a time + // if the viewport isn't filled yet + const addIdx = this.clampIndex(idx, this.maxAddIdx); + const addValue = addIdx === idx ? value : getIteratorValueAtIdx(list[Symbol.iterator](), addIdx)!; + return this.createAddResult(addIdx, addValue); + } else { + // if the add happened after the range, we only update the range with the new length + return {type: ResultType.UpdateRange, newRange: this.derive(1, 0)}; + } + } + + queryRemove(idx: number, list: Iterable): AddRemoveResult { + if (idx < this.end) { + const removeIdx = this.clampIndex(idx); + return this.createRemoveResult(removeIdx, list); + } else { + return {type: ResultType.UpdateRange, newRange: this.derive(-1, 0)}; + } + } + + queryMove(fromIdx: number, toIdx: number, value: T, list: Iterable): MoveResult | AddRemoveResult | undefined { + const fromZone = this.getIndexZone(fromIdx); + const toZone = this.getIndexZone(toIdx); + if (fromZone === toZone) { + if (fromZone === RangeZone.Before || fromZone === RangeZone.After) { + return; + } else if (fromZone === RangeZone.Inside) { + return {type: ResultType.Move, fromIdx, toIdx}; + } + } else { + // TODO + const addIdx = this.clampIndex(toIdx); + const removeIdx = this.clampIndex(fromIdx); + const addValue = addIdx === toIdx ? value : getIteratorValueAtIdx(list[Symbol.iterator](), addIdx)!; + return {type: ResultType.RemoveAndAdd, removeIdx, addIdx, value: addValue}; + } + } + + private createAddResult(addIdx: number, value: T): AddRemoveResult { + // if the view port isn't filled yet, we don't remove + if (this.viewportItemCount > this.length) { + return {type: ResultType.Add, addIdx, value, newRange: this.derive(1, 1)}; + } else { + const removeIdx = this.clampIndex(Number.MAX_SAFE_INTEGER); + return {type: ResultType.RemoveAndAdd, removeIdx, addIdx, value, newRange: this.derive(1, 0)}; + } + } + + private createRemoveResult(removeIdx: number, list: Iterable): AddRemoveResult { + if (this.end < this.totalLength) { + // we have items below the range, we can add one from there to fill the viewport + const addIdx = this.clampIndex(Number.MAX_SAFE_INTEGER); + // we assume the value has already been removed from the list, + // so we can just look up the next value which is already at the same idx + const value = getIteratorValueAtIdx(list[Symbol.iterator](), addIdx)!; + return {type: ResultType.RemoveAndAdd, removeIdx, value, addIdx, newRange: this.derive(-1, 0)}; + } else if (this.start !== 0) { + // move the range 1 item up so we still display a viewport full of items + const newRange = this.derive(-1, 0, 1); + const addIdx = newRange.start; + // we assume the value has already been removed from the list, + // so we can just look up the next value which is already at the same idx + const value = getIteratorValueAtIdx(list[Symbol.iterator](), addIdx)!; + return {type: ResultType.RemoveAndAdd, removeIdx, value, addIdx, newRange}; + } else { + // we can't add at the bottom nor top, already constrained + return {type: ResultType.Remove, removeIdx, newRange: this.derive(-1, 0)}; + } + } + + private derive(totalLengthInc: number, viewportItemCountDecr: number, startDecr: number = 0): ListRange { + return new ListRange( + this.start - startDecr, + this.end - startDecr + viewportItemCountDecr, + this.totalLength + totalLengthInc, + this.viewportItemCount + ); + } +} + +import {ObservableArray} from "../../../../observable/list/ObservableArray.js"; + +export function tests() { + return { + "fromViewport": assert => { + const range = ListRange.fromViewport(10, 20, 90, 30); + assert.equal(range.start, 1); + assert.equal(range.end, 6); + assert.equal(range.totalLength, 10); + }, + "fromViewport at end": assert => { + const itemHeight = 20; + const range = ListRange.fromViewport(10, itemHeight, 3 * itemHeight, 7 * itemHeight); + assert.equal(range.start, 7); + assert.equal(range.end, 10); + assert.equal(range.totalLength, 10); + }, + "fromViewport with not enough items to fill viewport": assert => { + const itemHeight = 20; + const range = ListRange.fromViewport(5, itemHeight, 8 * itemHeight, 0); + assert.equal(range.start, 0); + assert.equal(range.end, 5); + assert.equal(range.totalLength, 5); + assert.equal(range.length, 5); + assert.equal(range.viewportItemCount, 8); + }, + "expand at start of list": assert => { + const range = new ListRange(1, 5, 10); + const expanded = range.expand(2); + assert.equal(expanded.start, 0); + assert.equal(expanded.end, 7); + assert.equal(expanded.totalLength, 10); + assert.equal(expanded.length, 7); + }, + "expand at end of list": assert => { + const range = new ListRange(7, 9, 10); + const expanded = range.expand(2); + assert.equal(expanded.start, 5); + assert.equal(expanded.end, 10); + assert.equal(expanded.totalLength, 10); + assert.equal(expanded.length, 5); + }, + "expand in middle of list": assert => { + const range = new ListRange(4, 6, 10); + const expanded = range.expand(2); + assert.equal(expanded.start, 2); + assert.equal(expanded.end, 8); + assert.equal(expanded.totalLength, 10); + assert.equal(expanded.length, 6); + }, + "queryAdd with addition before range": assert => { + const list = new ObservableArray(["b", "c", "d", "e"]); + const range = new ListRange(1, 3, list.length); + let added = false; + list.subscribe({ + onAdd(idx, value) { + added = true; + const result = range.queryAdd(idx, value, list); + assert.deepEqual(result, { + type: ResultType.RemoveAndAdd, + removeIdx: 2, + addIdx: 1, + value: "b", + newRange: new ListRange(1, 3, 5) + }); + } + }); + list.insert(0, "a"); + assert(added); + }, + "queryAdd with addition within range": assert => { + const list = new ObservableArray(["a", "b", "d", "e"]); + const range = new ListRange(1, 3, list.length); + let added = false; + list.subscribe({ + onAdd(idx, value) { + added = true; + const result = range.queryAdd(idx, value, list); + assert.deepEqual(result, { + type: ResultType.RemoveAndAdd, + removeIdx: 2, + addIdx: 2, + value: "c", + newRange: new ListRange(1, 3, 5) + }); + } + }); + list.insert(2, "c"); + assert(added); + }, + "queryAdd with addition after range": assert => { + const list = new ObservableArray(["a", "b", "c", "d"]); + const range = new ListRange(1, 3, list.length); + let added = false; + list.subscribe({ + onAdd(idx, value) { + added = true; + const result = range.queryAdd(idx, value, list); + assert.deepEqual(result, { + type: ResultType.UpdateRange, + newRange: new ListRange(1, 3, 5) + }); + } + }); + list.insert(4, "e"); + assert(added); + }, + "queryAdd with too few items to fill viewport grows the range": assert => { + const list = new ObservableArray(["a", "b", "d"]); + const viewportItemCount = 4; + const range = new ListRange(0, 3, list.length, viewportItemCount); + let added = false; + list.subscribe({ + onAdd(idx, value) { + added = true; + const result = range.queryAdd(idx, value, list); + assert.deepEqual(result, { + type: ResultType.Add, + newRange: new ListRange(0, 4, 4), + addIdx: 2, + value: "c" + }); + } + }); + list.insert(2, "c"); + assert(added); + }, + "queryRemove with removal before range": assert => { + const list = new ObservableArray(["a", "b", "c", "d", "e"]); + const range = new ListRange(1, 3, list.length); + let removed = false; + list.subscribe({ + onRemove(idx) { + removed = true; + const result = range.queryRemove(idx, list); + assert.deepEqual(result, { + type: ResultType.RemoveAndAdd, + removeIdx: 1, + addIdx: 2, + value: "d", + newRange: new ListRange(1, 3, 4) + }); + } + }); + list.remove(0); + assert(removed); + }, + "queryRemove with removal within range": assert => { + const list = new ObservableArray(["a", "b", "c", "d", "e"]); + const range = new ListRange(1, 3, list.length); + let removed = false; + list.subscribe({ + onRemove(idx) { + removed = true; + const result = range.queryRemove(idx, list); + assert.deepEqual(result, { + type: ResultType.RemoveAndAdd, + removeIdx: 2, + addIdx: 2, + value: "d", + newRange: new ListRange(1, 3, 4) + }); + assert.equal(list.length, 4); + } + }); + list.remove(2); + assert(removed); + }, + "queryRemove with removal after range": assert => { + const list = new ObservableArray(["a", "b", "c", "d", "e"]); + const range = new ListRange(1, 3, list.length); + let removed = false; + list.subscribe({ + onRemove(idx) { + removed = true; + const result = range.queryRemove(idx, list); + assert.deepEqual(result, { + type: ResultType.UpdateRange, + newRange: new ListRange(1, 3, 4) + }); + } + }); + list.remove(3); + assert(removed); + }, + "queryRemove at bottom of range moves range one up": assert => { + const list = new ObservableArray(["a", "b", "c"]); + const range = new ListRange(1, 3, list.length); + let removed = false; + list.subscribe({ + onRemove(idx) { + removed = true; + const result = range.queryRemove(idx, list); + assert.deepEqual(result, { + newRange: new ListRange(0, 2, 2), + type: ResultType.RemoveAndAdd, + removeIdx: 2, + addIdx: 0, + value: "a" + }); + } + }); + list.remove(2); + assert(removed); + }, + + "queryMove with move inside range": assert => { + const list = new ObservableArray(["a", "b", "c", "d", "e"]); + const range = new ListRange(1, 4, list.length); + let moved = false; + list.subscribe({ + onMove(fromIdx, toIdx, value) { + moved = true; + const result = range.queryMove(fromIdx, toIdx, value, list); + assert.deepEqual(result, { + type: ResultType.Move, + fromIdx: 2, + toIdx: 3 + }); + } + }); + list.move(2, 3); + assert(moved); + }, + + }; +} + +// TODO: test with view larger than space needed by list diff --git a/src/platform/web/ui/general/ItemRange.ts b/src/platform/web/ui/general/Range.ts similarity index 78% rename from src/platform/web/ui/general/ItemRange.ts rename to src/platform/web/ui/general/Range.ts index 66db6078..16dc0a33 100644 --- a/src/platform/web/ui/general/ItemRange.ts +++ b/src/platform/web/ui/general/Range.ts @@ -17,7 +17,7 @@ limitations under the License. // start is included in the range, // end is excluded, // so [2, 2[ means an empty range -class Range { +export class Range { constructor( public readonly start: number, public readonly end: number @@ -35,14 +35,12 @@ class Range { return idx >= this.start && idx < this.end; } - intersects(range: Range): boolean { - return range.start < this.end && this.start < range.end; + toLocalIndex(idx: number) { + return idx - this.start; } - forEach(callback: ((i: number) => void)) { - for (let i = this.start; i < this.end; i += 1) { - callback(i); - } + intersects(range: Range): boolean { + return range.start < this.end && this.start < range.end; } forEachInIterator(it: IterableIterator, callback: ((T, i: number) => void)) { @@ -63,6 +61,30 @@ class Range { [Symbol.iterator](): Iterator { return new RangeIterator(this); } + + reverseIterable(): Iterable { + return new ReverseRangeIterator(this); + } + + clampIndex(idx: number, end = this.end - 1) { + return Math.min(Math.max(this.start, idx), end); + } + + getIndexZone(idx): RangeZone { + if (idx < this.start) { + return RangeZone.Before; + } else if (idx < this.end) { + return RangeZone.Inside; + } else { + return RangeZone.After; + } + } +} + +export enum RangeZone { + Before = 1, + Inside, + After } class RangeIterator implements Iterator { @@ -81,6 +103,26 @@ class RangeIterator implements Iterator { } } +class ReverseRangeIterator implements Iterable, Iterator { + private idx: number; + constructor(private readonly range: Range) { + this.idx = range.end; + } + + [Symbol.iterator]() { + return this; + } + + next(): IteratorResult { + if (this.idx > this.range.start) { + this.idx -= 1; + return {value: this.idx, done: false}; + } else { + return {value: undefined, done: true}; + } + } +} + export function tests() { return { "length": assert => { @@ -90,6 +132,9 @@ export function tests() { "iterator": assert => { assert.deepEqual(Array.from(new Range(2, 5)), [2, 3, 4]); }, + "reverseIterable": assert => { + assert.deepEqual(Array.from(new Range(2, 5).reverseIterable()), [4, 3, 2]); + }, "containsIndex": assert => { const a = new Range(2, 5); assert.equal(a.containsIndex(0), false); @@ -168,43 +213,13 @@ export function tests() { {v: "c", i: 2}, ]); }, + "clampIndex": assert => { + assert.equal(new Range(2, 5).clampIndex(0), 2); + assert.equal(new Range(2, 5).clampIndex(2), 2); + assert.equal(new Range(2, 5).clampIndex(3), 3); + assert.equal(new Range(2, 5).clampIndex(4), 4); + assert.equal(new Range(2, 5).clampIndex(5), 4); + assert.equal(new Range(2, 5).clampIndex(10), 4); + } }; } - -export class ItemRange extends Range { - constructor( - start: number, - end: number, - public readonly totalLength: number - ) { - super(start, end); - } - - - expand(amount: number): ItemRange { - // don't expand ranges that won't render anything - if (this.length === 0) { - return this; - } - - const topGrow = Math.min(amount, this.start); - const bottomGrow = Math.min(amount, this.totalLength - this.end); - return new ItemRange( - this.start - topGrow, - this.end + topGrow + bottomGrow, - this.totalLength, - ); - } - - static fromViewport(listLength: number, itemHeight: number, listHeight: number, scrollTop: number) { - const topCount = Math.min(Math.max(0, Math.floor(scrollTop / itemHeight)), listLength); - const itemsAfterTop = listLength - topCount; - const visibleItems = listHeight !== 0 ? Math.ceil(listHeight / itemHeight) : 0; - const renderCount = Math.min(visibleItems, itemsAfterTop); - return new ItemRange(topCount, topCount + renderCount, listLength); - } - - missingFrom() { - - } -} From 3aa3b7e1600e342cd3f02b1486c540ff86342e9b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 08:30:52 +0100 Subject: [PATCH 147/174] fix end growing larger than totalLength when range shrinks in case of remove --- src/platform/web/ui/general/ListRange.ts | 47 +++++++++++++++++------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/platform/web/ui/general/ListRange.ts b/src/platform/web/ui/general/ListRange.ts index 7ccd5726..a85a7323 100644 --- a/src/platform/web/ui/general/ListRange.ts +++ b/src/platform/web/ui/general/ListRange.ts @@ -131,12 +131,12 @@ export class ListRange extends Range { if (idx <= maxAddIdx) { // use maxAddIdx to allow to grow the range by one at a time // if the viewport isn't filled yet - const addIdx = this.clampIndex(idx, this.maxAddIdx); + const addIdx = this.clampIndex(idx, maxAddIdx); const addValue = addIdx === idx ? value : getIteratorValueAtIdx(list[Symbol.iterator](), addIdx)!; return this.createAddResult(addIdx, addValue); } else { // if the add happened after the range, we only update the range with the new length - return {type: ResultType.UpdateRange, newRange: this.derive(1, 0)}; + return {type: ResultType.UpdateRange, newRange: this.deriveRange(1, 0)}; } } @@ -145,7 +145,7 @@ export class ListRange extends Range { const removeIdx = this.clampIndex(idx); return this.createRemoveResult(removeIdx, list); } else { - return {type: ResultType.UpdateRange, newRange: this.derive(-1, 0)}; + return {type: ResultType.UpdateRange, newRange: this.deriveRange(-1, 0)}; } } @@ -170,10 +170,10 @@ export class ListRange extends Range { private createAddResult(addIdx: number, value: T): AddRemoveResult { // if the view port isn't filled yet, we don't remove if (this.viewportItemCount > this.length) { - return {type: ResultType.Add, addIdx, value, newRange: this.derive(1, 1)}; + return {type: ResultType.Add, addIdx, value, newRange: this.deriveRange(1, 1)}; } else { const removeIdx = this.clampIndex(Number.MAX_SAFE_INTEGER); - return {type: ResultType.RemoveAndAdd, removeIdx, addIdx, value, newRange: this.derive(1, 0)}; + return {type: ResultType.RemoveAndAdd, removeIdx, addIdx, value, newRange: this.deriveRange(1, 0)}; } } @@ -184,10 +184,10 @@ export class ListRange extends Range { // we assume the value has already been removed from the list, // so we can just look up the next value which is already at the same idx const value = getIteratorValueAtIdx(list[Symbol.iterator](), addIdx)!; - return {type: ResultType.RemoveAndAdd, removeIdx, value, addIdx, newRange: this.derive(-1, 0)}; + return {type: ResultType.RemoveAndAdd, removeIdx, value, addIdx, newRange: this.deriveRange(-1, 0)}; } else if (this.start !== 0) { // move the range 1 item up so we still display a viewport full of items - const newRange = this.derive(-1, 0, 1); + const newRange = this.deriveRange(-1, 0, 1); const addIdx = newRange.start; // we assume the value has already been removed from the list, // so we can just look up the next value which is already at the same idx @@ -195,15 +195,19 @@ export class ListRange extends Range { return {type: ResultType.RemoveAndAdd, removeIdx, value, addIdx, newRange}; } else { // we can't add at the bottom nor top, already constrained - return {type: ResultType.Remove, removeIdx, newRange: this.derive(-1, 0)}; + return {type: ResultType.Remove, removeIdx, newRange: this.deriveRange(-1, 0)}; } } - private derive(totalLengthInc: number, viewportItemCountDecr: number, startDecr: number = 0): ListRange { + private deriveRange(totalLengthInc: number, viewportItemCountDecr: number, startDecr: number = 0): ListRange { + const start = this.start - startDecr; + const totalLength = this.totalLength + totalLengthInc; + // prevent end being larger than totalLength + const end = Math.min(Math.max(start, this.end - startDecr + viewportItemCountDecr), totalLength); return new ListRange( - this.start - startDecr, - this.end - startDecr + viewportItemCountDecr, - this.totalLength + totalLengthInc, + start, + end, + totalLength, this.viewportItemCount ); } @@ -414,7 +418,24 @@ export function tests() { list.remove(2); assert(removed); }, - + "queryRemove with range on full length shrinks range": assert => { + const list = new ObservableArray(["a", "b", "c"]); + const range = new ListRange(0, 3, list.length); + let removed = false; + list.subscribe({ + onRemove(idx) { + removed = true; + const result = range.queryRemove(idx, list); + assert.deepEqual(result, { + newRange: new ListRange(0, 2, 2, 3), + type: ResultType.Remove, + removeIdx: 2, + }); + } + }); + list.remove(2); + assert(removed); + }, "queryMove with move inside range": assert => { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(1, 4, list.length); From c22718811fa351e0dfa3e447673bffaebab1df6e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 08:56:33 +0100 Subject: [PATCH 148/174] more tests for queryMove --- src/observable/list/ObservableArray.js | 2 +- src/platform/web/ui/general/ListRange.ts | 97 +++++++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/observable/list/ObservableArray.js b/src/observable/list/ObservableArray.js index 053e7320..afdaba37 100644 --- a/src/observable/list/ObservableArray.js +++ b/src/observable/list/ObservableArray.js @@ -46,7 +46,7 @@ export class ObservableArray extends BaseObservableList { move(fromIdx, toIdx) { if (fromIdx < this._items.length && toIdx < this._items.length) { - const item = this._items.splice(fromIdx, 1); + const [item] = this._items.splice(fromIdx, 1); this._items.splice(toIdx, 0, item); this.emitMove(fromIdx, toIdx, item); } diff --git a/src/platform/web/ui/general/ListRange.ts b/src/platform/web/ui/general/ListRange.ts index a85a7323..3604e5f8 100644 --- a/src/platform/web/ui/general/ListRange.ts +++ b/src/platform/web/ui/general/ListRange.ts @@ -454,8 +454,101 @@ export function tests() { list.move(2, 3); assert(moved); }, + "queryMove with move from before to inside range": assert => { + const list = new ObservableArray(["a", "b", "c", "d", "e"]); + const range = new ListRange(2, 5, list.length); + let moved = false; + list.subscribe({ + onMove(fromIdx, toIdx, value) { + moved = true; + const result = range.queryMove(fromIdx, toIdx, value, list); + assert.deepEqual(result, { + type: ResultType.RemoveAndAdd, + removeIdx: 2, + addIdx: 3, + value: "a" + }); + } + }); + list.move(0, 3); // move "a" to after "d" + assert(moved); + }, + "queryMove with move from after to inside range": assert => { + const list = new ObservableArray(["a", "b", "c", "d", "e"]); + const range = new ListRange(0, 3, list.length); + let moved = false; + list.subscribe({ + onMove(fromIdx, toIdx, value) { + moved = true; + const result = range.queryMove(fromIdx, toIdx, value, list); + assert.deepEqual(result, { + type: ResultType.RemoveAndAdd, + removeIdx: 2, + addIdx: 1, + value: "e" + }); + } + }); + list.move(4, 1); // move "e" to before "b" + assert(moved); + }, + "queryMove with move inside range to after": assert => { + const list = new ObservableArray(["a", "b", "c", "d", "e"]); + const range = new ListRange(0, 3, list.length); + let moved = false; + list.subscribe({ + onMove(fromIdx, toIdx, value) { + moved = true; + const result = range.queryMove(fromIdx, toIdx, value, list); + assert.deepEqual(result, { + type: ResultType.RemoveAndAdd, + removeIdx: 1, + addIdx: 2, + value: "d" + }); + } + }); + list.move(1, 3); // move "b" to after "d" + assert(moved); + }, + "queryMove with move inside range to before": assert => { + const list = new ObservableArray(["a", "b", "c", "d", "e"]); + const range = new ListRange(2, 5, list.length); + let moved = false; + list.subscribe({ + onMove(fromIdx, toIdx, value) { + moved = true; + const result = range.queryMove(fromIdx, toIdx, value, list); + assert.deepEqual(result, { + type: ResultType.RemoveAndAdd, + removeIdx: 3, + addIdx: 2, + value: "b" + }); + } + }); + list.move(3, 0); // move "d" to before "a" + assert(moved); + }, + "queryMove with move from before range to after": assert => { + const list = new ObservableArray(["a", "b", "c", "d", "e"]); + const range = new ListRange(1, 4, list.length); + let moved = false; + list.subscribe({ + onMove(fromIdx, toIdx, value) { + moved = true; + const result = range.queryMove(fromIdx, toIdx, value, list); + assert.deepEqual(result, { + type: ResultType.RemoveAndAdd, + removeIdx: 1, + addIdx: 3, + value: "e" + }); + } + }); + list.move(0, 4); // move "a" to after "e" + assert(moved); + }, }; } - -// TODO: test with view larger than space needed by list From 7897ea88cd71ce4ea04cfa95119489a60811cefa Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 14:24:43 +0100 Subject: [PATCH 149/174] add some spaces and comments --- src/platform/web/ui/general/LazyListView.ts | 2 +- src/platform/web/ui/general/ListRange.ts | 2 ++ src/platform/web/ui/session/rightpanel/MemberListView.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.ts b/src/platform/web/ui/general/LazyListView.ts index f8aa80cc..2d8891ce 100644 --- a/src/platform/web/ui/general/LazyListView.ts +++ b/src/platform/web/ui/general/LazyListView.ts @@ -34,7 +34,7 @@ export class LazyListView extends ListView { private scrollContainer?: HTMLElement; constructor( - {itemHeight, overflowMargin = 5, overflowItems = 20,...options}: IOptions, + {itemHeight, overflowMargin = 5, overflowItems = 20, ...options}: IOptions, childCreator: (value: T) => V ) { super(options, childCreator); diff --git a/src/platform/web/ui/general/ListRange.ts b/src/platform/web/ui/general/ListRange.ts index 3604e5f8..2b70a987 100644 --- a/src/platform/web/ui/general/ListRange.ts +++ b/src/platform/web/ui/general/ListRange.ts @@ -67,6 +67,8 @@ interface RemoveResult { removeIdx: number; } +// need to repeat the fields from RemoveResult and AddResult here +// to make the discriminated union work interface RemoveAndAddResult { type: ResultType.RemoveAndAdd; newRange?: ListRange; diff --git a/src/platform/web/ui/session/rightpanel/MemberListView.js b/src/platform/web/ui/session/rightpanel/MemberListView.js index 6096f919..a4a4e78c 100644 --- a/src/platform/web/ui/session/rightpanel/MemberListView.js +++ b/src/platform/web/ui/session/rightpanel/MemberListView.js @@ -17,7 +17,7 @@ limitations under the License. import {LazyListView} from "../../general/LazyListView"; import {MemberTileView} from "./MemberTileView.js"; -export class MemberListView extends LazyListView{ +export class MemberListView extends LazyListView { constructor(vm) { super({ list: vm.memberTileViewModels, From c64a9c1e23d64f198bb688c776b880c8ab4394e3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 14:25:00 +0100 Subject: [PATCH 150/174] snowpack/esbuild 0.9 doesn't support override keyword --- src/platform/web/ui/general/LazyListView.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/web/ui/general/LazyListView.ts b/src/platform/web/ui/general/LazyListView.ts index 2d8891ce..deceac08 100644 --- a/src/platform/web/ui/general/LazyListView.ts +++ b/src/platform/web/ui/general/LazyListView.ts @@ -63,7 +63,8 @@ export class LazyListView extends ListView { } } - override async loadList() { + // override + async loadList() { /* Wait two frames for the return from mount() to be inserted into DOM. This should be enough, but if this gives us trouble we can always use From 4be2f12a14ee8b571c19ac82af20067c25a66d4f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 14:25:22 +0100 Subject: [PATCH 151/174] subscribe before calling list.length --- src/platform/web/ui/general/LazyListView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/general/LazyListView.ts b/src/platform/web/ui/general/LazyListView.ts index deceac08..a29e434c 100644 --- a/src/platform/web/ui/general/LazyListView.ts +++ b/src/platform/web/ui/general/LazyListView.ts @@ -76,10 +76,10 @@ export class LazyListView extends ListView { if (!this._list) { return; } + this._subscription = this._list.subscribe(this); const visibleRange = this._getVisibleRange(); this.renderRange = visibleRange.expand(this.overflowItems); this._childInstances = []; - this._subscription = this._list.subscribe(this); this.reRenderFullRange(this.renderRange); } From 9557178ffbb7303b1a74d3a46d03616e3256c9b5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 14:25:35 +0100 Subject: [PATCH 152/174] padding needs to be on ul, not scroll container, or the list blows up --- src/platform/web/ui/general/LazyListView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.ts b/src/platform/web/ui/general/LazyListView.ts index a29e434c..4686c5ea 100644 --- a/src/platform/web/ui/general/LazyListView.ts +++ b/src/platform/web/ui/general/LazyListView.ts @@ -131,7 +131,7 @@ export class LazyListView extends ListView { private adjustPadding(range: ListRange) { const paddingTop = range.start * this.itemHeight; const paddingBottom = (range.totalLength - range.end) * this.itemHeight; - const style = this.scrollContainer!.style; + const style = this._listElement!.style; style.paddingTop = `${paddingTop}px`; style.paddingBottom = `${paddingBottom}px`; } @@ -153,8 +153,8 @@ export class LazyListView extends ListView { return this.scrollContainer; } - private get _listElement(): Element | undefined { - return super.root(); + private get _listElement(): HTMLElement | undefined { + return super.root() as HTMLElement | undefined; } onAdd(idx: number, value: T) { From 35fb84c2759b34dc7ef26dfa894b8d4ab700373b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 14:26:15 +0100 Subject: [PATCH 153/174] remove old js lazylist --- .../web/ui/general/foo-LazyListView.js | 287 ------------------ 1 file changed, 287 deletions(-) delete mode 100644 src/platform/web/ui/general/foo-LazyListView.js diff --git a/src/platform/web/ui/general/foo-LazyListView.js b/src/platform/web/ui/general/foo-LazyListView.js deleted file mode 100644 index 3213ffee..00000000 --- a/src/platform/web/ui/general/foo-LazyListView.js +++ /dev/null @@ -1,287 +0,0 @@ -/* -Copyright 2021 The Matrix.org Foundation C.I.C. - -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 {el} from "./html"; -import {mountView} from "./utils"; -import {ListView} from "./ListView"; -import {insertAt} from "./utils"; -import {ItemRange, ScrollDirection} from "./ItemRange.js"; - -export class LazyListView extends ListView { - constructor({itemHeight, overflowMargin = 5, overflowItems = 20,...options}, childCreator) { - super(options, childCreator); - this._itemHeight = itemHeight; - this._overflowMargin = overflowMargin; - this._overflowItems = overflowItems; - } - - _getVisibleRange() { - const length = this._list ? this._list.length : 0; - const scrollTop = this._parent.scrollTop; - const topCount = Math.min(Math.max(0, Math.floor(scrollTop / this._itemHeight)), length); - const itemsAfterTop = length - topCount; - const visibleItems = this._height !== 0 ? Math.ceil(this._height / this._itemHeight) : 0; - const renderCount = Math.min(visibleItems, itemsAfterTop); - const bottomCount = itemsAfterTop - renderCount; - return new ItemRange(topCount, renderCount, bottomCount); - } - - _renderIfNeeded() { - const range = this._getVisibleRange(); - const intersectRange = range.expand(this._overflowMargin); - const renderRange = range.expand(this._overflowItems); - // only update render Range if the new range + overflowMargin isn't contained by the old anymore - if (!this._renderRange.contains(intersectRange)) { - console.log("new", renderRange); - console.log("current", this._renderRange); - console.log("diff", this._renderRange.diff(renderRange)); - this._renderElementsInRange(renderRange); - } - } - - async _initialRender() { - /* - Wait two frames for the return from mount() to be inserted into DOM. - This should be enough, but if this gives us trouble we can always use - MutationObserver. - */ - await new Promise(r => requestAnimationFrame(r)); - await new Promise(r => requestAnimationFrame(r)); - - this._height = this._parent.clientHeight; - if (this._height === 0) { console.error("LazyListView could not calculate parent height."); } - const initialRange = this._getVisibleRange(); - const initialRenderRange = initialRange.expand(this._overflowItems); - this._renderRange = new ItemRange(0, 0, initialRange.bottomCount + 1); - this._renderElementsInRange(initialRenderRange); - } - - _itemsFromList({start, end}) { - const array = []; - let i = 0; - for (const item of this._list) { - if (i >= start && i <= end) { - array.push(item); - } - i = i + 1; - } - return array; - } - - _itemAtIndex(idx) { - let i = 0; - for (const item of this._list) { - if (i === idx) { - return item; - } - i = i + 1; - } - return null; - } - - _adjustPadding(range) { - const { topCount, bottomCount } = range; - const paddingTop = topCount * this._itemHeight; - const paddingBottom = bottomCount * this._itemHeight; - this._root.style.paddingTop = `${paddingTop}px`; - this._root.style.paddingBottom = `${paddingBottom}px`; - } - - _renderedFragment(items, childInstanceModifier) { - const fragment = document.createDocumentFragment(); - for (const item of items) { - const view = this._childCreator(item); - childInstanceModifier(view); - fragment.appendChild(mountView(view, this._mountArgs)); - } - return fragment; - } - - _renderElementsInRange(range) { - const diff = this._renderRange.diff(range); - const renderedItems = this._itemsFromList(diff.toAdd); - this._adjustPadding(range); - const {start, end} = diff.toRemove; - const normalizedStart = this._renderRange.normalize(start); - this._childInstances.splice(normalizedStart, end - start + 1).forEach(child => this._removeChild(child)); - - if (diff.scrollDirection === ScrollDirection.downwards) { - const fragment = this._renderedFragment(renderedItems, view => this._childInstances.push(view)); - this._root.appendChild(fragment); - } - else { - const fragment = this._renderedFragment(renderedItems, view => this._childInstances.unshift(view)); - this._root.insertBefore(fragment, this._root.firstChild); - } - this._renderRange = range; - } - - mount() { - const root = super.mount(); - this._subscription = this._list.subscribe(this); - this._childInstances = []; - this._parent = el("div", {className: "LazyListParent"}, root); - /* - Hooking to scroll events can be expensive. - Do we need to do more (like event throttling)? - */ - this._parent.addEventListener("scroll", () => this._renderIfNeeded()); - this._initialRender(); - return this._parent; - } - - update(attributes) { - this._renderRange = null; - super.update(attributes); - this._childInstances = []; - this._initialRender(); - } - - loadList() { - // We don't render the entire list; so nothing to see here. - } - - _removeChild(child) { - child.root().remove(); - child.unmount(); - } - - onAdd(idx, value) { - const {topCount, renderCount, bottomCount} = this._renderRange; - if (this._renderRange.containsIndex(idx)) { - this.onBeforeListChanged(); - const normalizedIdx = this._renderRange.normalize(idx); - if (bottomCount === 0) { - /* - If we're at the bottom of the list, we need to render the additional item - without removing another item from the list. - We can't increment topCount because the index topCount is not affected by the - add operation (and any modification will thus break ItemRange.normalize()). - We can't increment bottomCount because there's not enough items left to trigger - a further render. - */ - this._renderRange = new ItemRange(topCount, renderCount + 1, bottomCount); - } - else { - // Remove the last element, render the new element - this._removeChild(this._childInstances.pop()); - this._renderRange = new ItemRange(topCount, renderCount, bottomCount + 1); - } - super.onAdd(normalizedIdx, value, true); - this.onListChanged(); - } - else { - this._renderRange = idx < topCount ? new ItemRange(topCount + 1, renderCount, bottomCount): - new ItemRange(topCount, renderCount, bottomCount + 1); - } - this._adjustPadding(this._renderRange); - } - - onRemove(idx, value) { - const {topCount, renderCount, bottomCount} = this._renderRange; - if (this._renderRange.containsIndex(idx)) { - this.onBeforeListChanged(); - const normalizedIdx = this._renderRange.normalize(idx); - super.onRemove(normalizedIdx, value, true); - if (bottomCount === 0) { - // See onAdd for explanation - this._renderRange = new ItemRange(topCount, renderCount - 1, bottomCount); - } - else { - const child = this._childCreator(this._itemAtIndex(this._renderRange.lastIndex)); - this._childInstances.push(child); - this._root.appendChild(mountView(child, this._mountArgs)); - this._renderRange = new ItemRange(topCount, renderCount, bottomCount - 1); - } - this.onListChanged(); - } - else { - this._renderRange = idx < topCount ? new ItemRange(topCount - 1, renderCount, bottomCount): - new ItemRange(topCount, renderCount, bottomCount - 1); - } - this._adjustPadding(this._renderRange); - } - - onUpdate(idx, value, params) { - if (this._renderRange.containsIndex(idx)) { - const normalizedIdx = this._renderRange.normalize(idx); - super.onUpdate(normalizedIdx, value, params); - } - } - - recreateItem(idx, value) { - if (this._renderRange.containsIndex(idx)) { - const normalizedIdx = this._renderRange.normalize(idx); - super.recreateItem(normalizedIdx, value) - } - } - - /** - * Render additional element from top or bottom to offset the outgoing element - */ - _renderExtraOnMove(fromIdx, toIdx) { - const {topCount, renderCount} = this._renderRange; - if (toIdx < fromIdx) { - // Element is moved up the list, so render element from top boundary - const index = topCount; - const child = this._childCreator(this._itemAtIndex(index)); - this._childInstances.unshift(child); - this._root.insertBefore(mountView(child, this._mountArgs), this._root.firstChild); - } - else { - // Element is moved down the list, so render element from bottom boundary - const index = topCount + renderCount - 1; - const child = this._childCreator(this._itemAtIndex(index)); - this._childInstances.push(child); - this._root.appendChild(mountView(child, this._mountArgs)); - } - } - - /** - * Remove an element from top or bottom to make space for the incoming element - */ - _removeElementOnMove(fromIdx, toIdx) { - // If element comes from the bottom, remove element at bottom and vice versa - const child = toIdx < fromIdx ? this._childInstances.pop() : this._childInstances.shift(); - this._removeChild(child); - } - - onMove(fromIdx, toIdx, value) { - const fromInRange = this._renderRange.containsIndex(fromIdx); - const toInRange = this._renderRange.containsIndex(toIdx); - const normalizedFromIdx = this._renderRange.normalize(fromIdx); - const normalizedToIdx = this._renderRange.normalize(toIdx); - if (fromInRange && toInRange) { - super.onMove(normalizedFromIdx, normalizedToIdx, value); - } - else if (fromInRange && !toInRange) { - this.onBeforeListChanged(); - const [child] = this._childInstances.splice(normalizedFromIdx, 1); - this._removeChild(child); - this._renderExtraOnMove(fromIdx, toIdx); - this.onListChanged(); - } - else if (!fromInRange && toInRange) { - this.onBeforeListChanged(); - const child = this._childCreator(value); - this._removeElementOnMove(fromIdx, toIdx); - this._childInstances.splice(normalizedToIdx, 0, child); - insertAt(this._root, normalizedToIdx, mountView(child, this._mountArgs)); - this.onListChanged(); - } - } - -} From e34a92e2ec2917addede180d12f1a34b0d39181d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 14:30:11 +0100 Subject: [PATCH 154/174] fix copyright --- src/platform/web/ui/general/LazyListView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/general/LazyListView.ts b/src/platform/web/ui/general/LazyListView.ts index 4686c5ea..81b57264 100644 --- a/src/platform/web/ui/general/LazyListView.ts +++ b/src/platform/web/ui/general/LazyListView.ts @@ -1,5 +1,5 @@ /* -Copyright 2020 Bruno Windels +Copyright 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 7b38df45da8d9e6e66e1a15c87082274cf813f02 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 14:31:23 +0100 Subject: [PATCH 155/174] i think this is fine now? --- src/platform/web/ui/general/ListRange.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/web/ui/general/ListRange.ts b/src/platform/web/ui/general/ListRange.ts index 2b70a987..80cd17e8 100644 --- a/src/platform/web/ui/general/ListRange.ts +++ b/src/platform/web/ui/general/ListRange.ts @@ -161,7 +161,6 @@ export class ListRange extends Range { return {type: ResultType.Move, fromIdx, toIdx}; } } else { - // TODO const addIdx = this.clampIndex(toIdx); const removeIdx = this.clampIndex(fromIdx); const addValue = addIdx === toIdx ? value : getIteratorValueAtIdx(list[Symbol.iterator](), addIdx)!; From e4be1702c4f6a0cd952c05bc053986622086d911 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 14:32:42 +0100 Subject: [PATCH 156/174] add comment for future test --- src/platform/web/ui/general/ListRange.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/web/ui/general/ListRange.ts b/src/platform/web/ui/general/ListRange.ts index 80cd17e8..4f62ba21 100644 --- a/src/platform/web/ui/general/ListRange.ts +++ b/src/platform/web/ui/general/ListRange.ts @@ -550,6 +550,7 @@ export function tests() { list.move(0, 4); // move "a" to after "e" assert(moved); }, - + // would be good to test here what multiple mutations look like with executing the result of queryXXX + // on an array, much like we do in the view. }; } From f444160c6a29b4766551a20c7364df001a78789a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Nov 2021 14:33:27 +0100 Subject: [PATCH 157/174] feels ok without overflow margin for now --- src/platform/web/ui/general/LazyListView.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/platform/web/ui/general/LazyListView.ts b/src/platform/web/ui/general/LazyListView.ts index 81b57264..1696f044 100644 --- a/src/platform/web/ui/general/LazyListView.ts +++ b/src/platform/web/ui/general/LazyListView.ts @@ -22,7 +22,6 @@ import {IView} from "./types"; export interface IOptions extends IParentOptions { itemHeight: number; - overflowMargin?: number; overflowItems?: number; } @@ -34,13 +33,12 @@ export class LazyListView extends ListView { private scrollContainer?: HTMLElement; constructor( - {itemHeight, overflowMargin = 5, overflowItems = 20, ...options}: IOptions, + {itemHeight, overflowItems = 20, ...options}: IOptions, childCreator: (value: T) => V ) { super(options, childCreator); this.itemHeight = itemHeight; this.overflowItems = overflowItems; - // TODO: this.overflowMargin = overflowMargin; } handleEvent(e: Event) { From 229c584138a1928a3bb764293330a84aac3baad5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 25 Nov 2021 15:38:13 +0100 Subject: [PATCH 158/174] don't fail login if dehydrated devices are not supported --- src/matrix/SessionContainer.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 3e3dab66..91418b0d 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -329,7 +329,16 @@ export class SessionContainer { request: this._platform.request, }); const olm = await this._olmPromise; - const encryptedDehydratedDevice = await getDehydratedDevice(hsApi, olm, this._platform, log); + let encryptedDehydratedDevice; + try { + encryptedDehydratedDevice = await getDehydratedDevice(hsApi, olm, this._platform, log); + } catch (err) { + if (err instanceof HomeServerError) { + log.set("not_supported", true); + } else { + throw err; + } + } if (encryptedDehydratedDevice) { let resolveStageFinish; const promiseStageFinish = new Promise(r => resolveStageFinish = r); From ae7d4d07dfbcbfbe82972594dad62bf571cb8757 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 25 Nov 2021 15:42:36 +0100 Subject: [PATCH 159/174] use .name so we don't need an import --- src/matrix/SessionContainer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index 91418b0d..d8977dde 100644 --- a/src/matrix/SessionContainer.js +++ b/src/matrix/SessionContainer.js @@ -333,7 +333,7 @@ export class SessionContainer { try { encryptedDehydratedDevice = await getDehydratedDevice(hsApi, olm, this._platform, log); } catch (err) { - if (err instanceof HomeServerError) { + if (err.name === "HomeServerError") { log.set("not_supported", true); } else { throw err; From 876fcf532f86dcc10aef65a5ce087deac3545022 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 26 Nov 2021 09:12:08 +0100 Subject: [PATCH 160/174] release v0.2.22 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3211bf4f..6479bee1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydrogen-web", - "version": "0.2.21", + "version": "0.2.22", "description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB", "main": "src/lib.ts", "directories": { From 2802164bb4f416d6d59c3b9bfb8ec2d9cbbb78d9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 26 Nov 2021 12:45:40 +0100 Subject: [PATCH 161/174] update to version that doesn't use a bash script anymore, which doesnt work on macos --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6479bee1..848e4104 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint": "^7.32.0", "fake-indexeddb": "^3.1.2", "finalhandler": "^1.1.1", - "impunity": "^1.0.3", + "impunity": "^1.0.7", "mdn-polyfills": "^5.20.0", "postcss": "^8.1.1", "postcss-css-variables": "^0.17.0", diff --git a/yarn.lock b/yarn.lock index 830a5cd5..52661eff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3021,10 +3021,10 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -impunity@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/impunity/-/impunity-1.0.3.tgz#1e9972d137f0e0dbed4d39e6eca6c238a29b6526" - integrity sha512-D3XT8ED/AX5mbSQdAf0nz+65ilZE/q17Yykx8g7FqlnKjDdoowFv9kxdS4tOggPrJY/5lAfE86/ZFa878VpZBw== +impunity@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/impunity/-/impunity-1.0.7.tgz#b1484678ca82b7fce7750ab085a418302ce5883d" + integrity sha512-DIPr9toJHP8s/SMe6efOzhfGtiCbEniPO4JtHk6CvSZqtEPZpkF31l8TUrG8XTYxU1lBFwlkFi7RZ681YWdBrQ== dependencies: colors "^1.3.3" commander "^6.1.0" From d981a852397b825f136e75d0a22efa3552fafaf8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 29 Nov 2021 11:43:43 +0530 Subject: [PATCH 162/174] Filter token out of stack trace --- src/logging/LogItem.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index da009b06..bb8815db 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -19,6 +19,11 @@ import {LogLevel, LogFilter} from "./LogFilter"; import type {BaseLogger} from "./BaseLogger"; import type {ISerializedItem, ILogItem, LogItemValues, LabelOrValues, FilterCreator, LogCallback} from "./types"; +// Make sure that loginToken does not end up in the logs +function filterLoginToken(trace?: string): string | undefined { + return trace?.replace(/(?<=\/\?loginToken=).+/, ""); +} + export class LogItem implements ILogItem { public readonly start: number; public logLevel: LogLevel; @@ -155,7 +160,7 @@ export class LogItem implements ILogItem { if (this.error) { // (e)rror item.e = { - stack: this.error.stack, + stack: filterLoginToken(this.error.stack), name: this.error.name, message: this.error.message.split("\n")[0] }; @@ -259,3 +264,14 @@ export class LogItem implements ILogItem { return this._children; } } + +export function tests() { + return { + "Login token removed from item": (assert) => { + const str = "main http://localhost:3000/src/main.js:55\n http://localhost:3000/?loginToken=secret:26"; + const result = filterLoginToken(str); + const index = result?.search("secret"); + assert.equal(index, -1); + } + } +} From 104590e34dbb278735005f237940bd7c2b43f118 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 29 Nov 2021 11:48:05 +0530 Subject: [PATCH 163/174] Use ! in test --- src/logging/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index bb8815db..4ddd687c 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -270,7 +270,7 @@ export function tests() { "Login token removed from item": (assert) => { const str = "main http://localhost:3000/src/main.js:55\n http://localhost:3000/?loginToken=secret:26"; const result = filterLoginToken(str); - const index = result?.search("secret"); + const index = result!.search("secret"); assert.equal(index, -1); } } From a134e48ebb8792d46774620652a93af2e7c6b822 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 29 Nov 2021 10:49:26 +0100 Subject: [PATCH 164/174] update impunity to 1.0.8 to run tests on node >= 16.12 --- package.json | 4 +- yarn.lock | 133 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 120 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 848e4104..8cda0543 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint": "^7.32.0", "fake-indexeddb": "^3.1.2", "finalhandler": "^1.1.1", - "impunity": "^1.0.7", + "impunity": "^1.0.8", "mdn-polyfills": "^5.20.0", "postcss": "^8.1.1", "postcss-css-variables": "^0.17.0", @@ -54,7 +54,6 @@ "xxhashjs": "^0.2.2" }, "dependencies": { - "node-html-parser": "^4.0.0", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "@rollup/plugin-commonjs": "^15.0.0", "@rollup/plugin-json": "^4.1.0", @@ -65,6 +64,7 @@ "bs58": "^4.0.1", "dompurify": "^2.3.0", "es6-promise": "https://github.com/bwindels/es6-promise.git#bwindels/expose-flush", + "node-html-parser": "^4.0.0", "rollup": "^2.26.4", "text-encoding": "^0.7.0" } diff --git a/yarn.lock b/yarn.lock index 52661eff..64926972 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2293,17 +2293,120 @@ es-module-lexer@^0.6.0: version "4.2.8" resolved "https://github.com/bwindels/es6-promise.git#112f78f5829e627055b0ff56a52fecb63f6003b1" -esbuild-node-loader@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/esbuild-node-loader/-/esbuild-node-loader-0.3.1.tgz#fa761e020eff316549ddc3c51a25ec9ffc159ff2" - integrity sha512-75xPOySulti9R5LwYOMmKiwHyXGr/f89I7UkHolLxRqeBiUNqcyCmNTjv+fO2WV9G3f7LDKVZJlbWoYh5LBpfQ== - dependencies: - esbuild "^0.12.6" +esbuild-android-arm64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz#3fc3ff0bab76fe35dd237476b5d2b32bb20a3d44" + integrity sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg== -esbuild@^0.12.6: - version "0.12.20" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.20.tgz#4d3c9d83c99a4031e027b42a4c398c23b6827cb0" - integrity sha512-u7+0qTo9Z64MD9PhooEngCmzyEYJ6ovFhPp8PLNh3UasR5Ihjv6HWVXqm8uHmasdQlpsAf0IsY4U0YVUfCpt4Q== +esbuild-darwin-64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz#8e9169c16baf444eacec60d09b24d11b255a8e72" + integrity sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ== + +esbuild-darwin-arm64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz#1b07f893b632114f805e188ddfca41b2b778229a" + integrity sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ== + +esbuild-freebsd-64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz#0b8b7eca1690c8ec94c75680c38c07269c1f4a85" + integrity sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA== + +esbuild-freebsd-arm64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz#2e1a6c696bfdcd20a99578b76350b41db1934e52" + integrity sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ== + +esbuild-linux-32@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz#6fd39f36fc66dd45b6b5f515728c7bbebc342a69" + integrity sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g== + +esbuild-linux-64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz#9cb8e4bcd7574e67946e4ee5f1f1e12386bb6dd3" + integrity sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA== + +esbuild-linux-arm64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz#3891aa3704ec579a1b92d2a586122e5b6a2bfba1" + integrity sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA== + +esbuild-linux-arm@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz#8a00e99e6a0c6c9a6b7f334841364d8a2b4aecfe" + integrity sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA== + +esbuild-linux-mips64le@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz#36b07cc47c3d21e48db3bb1f4d9ef8f46aead4f7" + integrity sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg== + +esbuild-linux-ppc64le@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz#f7e6bba40b9a11eb9dcae5b01550ea04670edad2" + integrity sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ== + +esbuild-netbsd-64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz#a2fedc549c2b629d580a732d840712b08d440038" + integrity sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w== + +esbuild-node-loader@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/esbuild-node-loader/-/esbuild-node-loader-0.6.3.tgz#3b90012f8bc2fcbb2ef76a659482c2c99840c5e8" + integrity sha512-Bf6o8SiMMh5+r20jsjAThNOtzo3t8Ye4Qdzz+twWHnxu28SdkGUr5ahq8iX0qbd+I9ge8sLNX7oQoNW1YzHlqA== + dependencies: + esbuild "^0.13.12" + +esbuild-openbsd-64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz#b22c0e5806d3a1fbf0325872037f885306b05cd7" + integrity sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g== + +esbuild-sunos-64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz#d0b6454a88375ee8d3964daeff55c85c91c7cef4" + integrity sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw== + +esbuild-windows-32@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz#c96d0b9bbb52f3303322582ef8e4847c5ad375a7" + integrity sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw== + +esbuild-windows-64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz#1f79cb9b1e1bb02fb25cd414cb90d4ea2892c294" + integrity sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ== + +esbuild-windows-arm64@0.13.15: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz#482173070810df22a752c686509c370c3be3b3c3" + integrity sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA== + +esbuild@^0.13.12: + version "0.13.15" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.15.tgz#db56a88166ee373f87dbb2d8798ff449e0450cdf" + integrity sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw== + optionalDependencies: + esbuild-android-arm64 "0.13.15" + esbuild-darwin-64 "0.13.15" + esbuild-darwin-arm64 "0.13.15" + esbuild-freebsd-64 "0.13.15" + esbuild-freebsd-arm64 "0.13.15" + esbuild-linux-32 "0.13.15" + esbuild-linux-64 "0.13.15" + esbuild-linux-arm "0.13.15" + esbuild-linux-arm64 "0.13.15" + esbuild-linux-mips64le "0.13.15" + esbuild-linux-ppc64le "0.13.15" + esbuild-netbsd-64 "0.13.15" + esbuild-openbsd-64 "0.13.15" + esbuild-sunos-64 "0.13.15" + esbuild-windows-32 "0.13.15" + esbuild-windows-64 "0.13.15" + esbuild-windows-arm64 "0.13.15" esbuild@~0.9.0: version "0.9.7" @@ -3021,14 +3124,14 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -impunity@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/impunity/-/impunity-1.0.7.tgz#b1484678ca82b7fce7750ab085a418302ce5883d" - integrity sha512-DIPr9toJHP8s/SMe6efOzhfGtiCbEniPO4JtHk6CvSZqtEPZpkF31l8TUrG8XTYxU1lBFwlkFi7RZ681YWdBrQ== +impunity@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/impunity/-/impunity-1.0.8.tgz#d6db44e8a15ca2cdaed6a5c478a770853cb5a56e" + integrity sha512-6jMqYrvY2SA/PZ+yheJYd3eJ3zcO8dWmHRVy/BSjnKMEmIVB+lMO30MZOkG+kHH0eJuaGOKv0BrZmgwE6NUDRg== dependencies: colors "^1.3.3" commander "^6.1.0" - esbuild-node-loader "^0.3.1" + esbuild-node-loader "^0.6.3" imurmurhash@^0.1.4: version "0.1.4" From 3322827979bc7068dc9f457476ea682667c7370c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 30 Nov 2021 08:04:45 +0100 Subject: [PATCH 165/174] upgrade impunity to propagate exit code --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8cda0543..017510d6 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint": "^7.32.0", "fake-indexeddb": "^3.1.2", "finalhandler": "^1.1.1", - "impunity": "^1.0.8", + "impunity": "^1.0.9", "mdn-polyfills": "^5.20.0", "postcss": "^8.1.1", "postcss-css-variables": "^0.17.0", diff --git a/yarn.lock b/yarn.lock index 64926972..11356c4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3124,10 +3124,10 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -impunity@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/impunity/-/impunity-1.0.8.tgz#d6db44e8a15ca2cdaed6a5c478a770853cb5a56e" - integrity sha512-6jMqYrvY2SA/PZ+yheJYd3eJ3zcO8dWmHRVy/BSjnKMEmIVB+lMO30MZOkG+kHH0eJuaGOKv0BrZmgwE6NUDRg== +impunity@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/impunity/-/impunity-1.0.9.tgz#8524d96f07c26987519ec693c4c4d3ab49254b03" + integrity sha512-tfy7GRHeE9JVURKM7dqfTAZItGFeA/DRrlhgMLUuzSig3jF+AYSUV26tGTMGrfCN0Cb9hNz6xrZnNwa5M1hz4Q== dependencies: colors "^1.3.3" commander "^6.1.0" From fe77b71c97d20e3d82c2ee39468e4550e195100d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 30 Nov 2021 13:28:28 +0530 Subject: [PATCH 166/174] use transformer function --- src/logging/BaseLogger.ts | 6 +++-- src/logging/IDBLogger.ts | 13 ++++++---- src/logging/LogItem.ts | 27 +++++++++----------- src/platform/web/Platform.js | 48 ++++++++++++++++++++++++++++++++---- 4 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/logging/BaseLogger.ts b/src/logging/BaseLogger.ts index 723c2f17..e32b9f0f 100644 --- a/src/logging/BaseLogger.ts +++ b/src/logging/BaseLogger.ts @@ -17,15 +17,17 @@ limitations under the License. import {LogItem} from "./LogItem"; import {LogLevel, LogFilter} from "./LogFilter"; -import type {ILogger, ILogExport, FilterCreator, LabelOrValues, LogCallback, ILogItem} from "./types"; +import type {ILogger, ILogExport, FilterCreator, LabelOrValues, LogCallback, ILogItem, ISerializedItem} from "./types"; import type {Platform} from "../platform/web/Platform.js"; export abstract class BaseLogger implements ILogger { protected _openItems: Set = new Set(); protected _platform: Platform; + protected _serializedTransformer: (item: ISerializedItem) => ISerializedItem; - constructor({platform}) { + constructor({platform, serializedTransformer = (item: ISerializedItem) => item}) { this._platform = platform; + this._serializedTransformer = serializedTransformer; } log(labelOrValues: LabelOrValues, logLevel: LogLevel = LogLevel.Info): void { diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 96147ca1..8ab81964 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -26,7 +26,7 @@ import {BaseLogger} from "./BaseLogger"; import type {Interval} from "../platform/web/dom/Clock"; import type {Platform} from "../platform/web/Platform.js"; import type {BlobHandle} from "../platform/web/dom/BlobHandle.js"; -import type {ILogItem, ILogExport} from "./types"; +import type {ILogItem, ILogExport, ISerializedItem} from "./types"; import type {LogFilter} from "./LogFilter"; type QueuedItem = { @@ -40,7 +40,7 @@ export class IDBLogger extends BaseLogger { private readonly _flushInterval: Interval; private _queuedItems: QueuedItem[]; - constructor(options: {name: string, flushInterval?: number, limit?: number, platform: Platform}) { + constructor(options: {name: string, flushInterval?: number, limit?: number, platform: Platform, serializedTransformer: (item: ISerializedItem) => ISerializedItem}) { super(options); const {name, flushInterval = 60 * 1000, limit = 3000} = options; this._name = name; @@ -119,9 +119,12 @@ export class IDBLogger extends BaseLogger { _persistItem(logItem: ILogItem, filter: LogFilter, forced: boolean): void { const serializedItem = logItem.serialize(filter, undefined, forced); - this._queuedItems.push({ - json: JSON.stringify(serializedItem) - }); + if (serializedItem) { + const transformedSerializedItem = this._serializedTransformer(serializedItem); + this._queuedItems.push({ + json: JSON.stringify(transformedSerializedItem) + }); + } } _persistQueuedItems(items: QueuedItem[]): void { diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 4ddd687c..5bd94f93 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -19,11 +19,6 @@ import {LogLevel, LogFilter} from "./LogFilter"; import type {BaseLogger} from "./BaseLogger"; import type {ISerializedItem, ILogItem, LogItemValues, LabelOrValues, FilterCreator, LogCallback} from "./types"; -// Make sure that loginToken does not end up in the logs -function filterLoginToken(trace?: string): string | undefined { - return trace?.replace(/(?<=\/\?loginToken=).+/, ""); -} - export class LogItem implements ILogItem { public readonly start: number; public logLevel: LogLevel; @@ -160,7 +155,7 @@ export class LogItem implements ILogItem { if (this.error) { // (e)rror item.e = { - stack: filterLoginToken(this.error.stack), + stack: this.error.stack, name: this.error.name, message: this.error.message.split("\n")[0] }; @@ -265,13 +260,13 @@ export class LogItem implements ILogItem { } } -export function tests() { - return { - "Login token removed from item": (assert) => { - const str = "main http://localhost:3000/src/main.js:55\n http://localhost:3000/?loginToken=secret:26"; - const result = filterLoginToken(str); - const index = result!.search("secret"); - assert.equal(index, -1); - } - } -} +// export function tests() { +// return { +// "Login token removed from item": (assert) => { +// const str = "main http://localhost:3000/src/main.js:55\n http://localhost:3000/?loginToken=secret:26"; +// const result = filterLoginToken(str); +// const index = result!.search("secret"); +// assert.equal(index, -1); +// } +// } +// } diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index f6cd2895..c871bb9e 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -132,11 +132,7 @@ export class Platform { this.clock = new Clock(); this.encoding = new Encoding(); this.random = Math.random; - if (options?.development) { - this.logger = new ConsoleLogger({platform: this}); - } else { - this.logger = new IDBLogger({name: "hydrogen_logs", platform: this}); - } + this._createLogger(options?.development); this.history = new History(); this.onlineStatus = new OnlineStatus(); this._serviceWorkerHandler = null; @@ -162,6 +158,21 @@ export class Platform { this._disposables = new Disposables(); } + _createLogger(isDevelopment) { + // Make sure that loginToken does not end up in the logs + const transformer = (item) => { + if (item.e?.stack) { + item.e.stack = item.e.stack.replace(/(?<=\/\?loginToken=).+/, ""); + } + return item; + }; + if (isDevelopment) { + this.logger = new ConsoleLogger({platform: this}); + } else { + this.logger = new IDBLogger({name: "hydrogen_logs", platform: this, serializedTransformer: transformer}); + } + } + get updateService() { return this._serviceWorkerHandler; } @@ -272,3 +283,30 @@ export class Platform { this._disposables.dispose(); } } + +import {LogItem} from "../../logging/LogItem"; +export function tests() { + return { + "loginToken should not be in logs": (assert) => { + const transformer = (item) => { + if (item.e?.stack) { + item.e.stack = item.e.stack.replace(/(?<=\/\?loginToken=).+/, ""); + } + return item; + }; + const logger = { + _queuedItems: [], + _serializedTransformer: transformer, + _now: () => {} + }; + logger.persist = IDBLogger.prototype._persistItem.bind(logger); + const logItem = new LogItem("test", 1, logger); + logItem.error = new Error(); + logItem.error.stack = "main http://localhost:3000/src/main.js:55\n http://localhost:3000/?loginToken=secret:26" + logger.persist(logItem, null, false); + const item = logger._queuedItems.pop(); + console.log(item); + assert.strictEqual(item.json.search("secret"), -1); + } + }; +} From 6699b71bd54cf70e43e9a0edfb41c8fb42bc92b6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 30 Nov 2021 13:38:25 +0530 Subject: [PATCH 167/174] transformer is optional --- src/logging/IDBLogger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/IDBLogger.ts b/src/logging/IDBLogger.ts index 8ab81964..ab9474b0 100644 --- a/src/logging/IDBLogger.ts +++ b/src/logging/IDBLogger.ts @@ -40,7 +40,7 @@ export class IDBLogger extends BaseLogger { private readonly _flushInterval: Interval; private _queuedItems: QueuedItem[]; - constructor(options: {name: string, flushInterval?: number, limit?: number, platform: Platform, serializedTransformer: (item: ISerializedItem) => ISerializedItem}) { + constructor(options: {name: string, flushInterval?: number, limit?: number, platform: Platform, serializedTransformer?: (item: ISerializedItem) => ISerializedItem}) { super(options); const {name, flushInterval = 60 * 1000, limit = 3000} = options; this._name = name; From 66fbc37ec4e45a6603737312117539753c288e6e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 30 Nov 2021 14:15:49 +0530 Subject: [PATCH 168/174] Remove comments --- src/logging/LogItem.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/logging/LogItem.ts b/src/logging/LogItem.ts index 5bd94f93..da009b06 100644 --- a/src/logging/LogItem.ts +++ b/src/logging/LogItem.ts @@ -259,14 +259,3 @@ export class LogItem implements ILogItem { return this._children; } } - -// export function tests() { -// return { -// "Login token removed from item": (assert) => { -// const str = "main http://localhost:3000/src/main.js:55\n http://localhost:3000/?loginToken=secret:26"; -// const result = filterLoginToken(str); -// const index = result!.search("secret"); -// assert.equal(index, -1); -// } -// } -// } From 2e57e99e34d853edfe4b645efaf1cbf9c9f248a4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 30 Nov 2021 14:15:25 +0000 Subject: [PATCH 169/174] clarify when to use type and interface --- doc/TS-MIGRATION.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/TS-MIGRATION.md b/doc/TS-MIGRATION.md index 7cb5bb34..006b3077 100644 --- a/doc/TS-MIGRATION.md +++ b/doc/TS-MIGRATION.md @@ -5,3 +5,11 @@ - find all methods and getters that throw or are empty in base classes and turn into abstract method or if all methods are abstract, into an interface. - change child impls to not call super.method and to add override - don't allow implicit override in ts config + +## Use `type` rather than `interface` for named parameters and POJO return values. + +`type` and `interface` can be used somewhat interchangebly used, but let's use `type` to describe data and `interface` to describe (polymorphic) behaviour. + +Good examples of data are option objects to have named parameters, and POJO (plain old javascript objects) without any methods, just fields. + +Also see [this playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBACghgJwgO2AeTMAlge2QZygF4oBvAKCiqmTgFsIAuKfYBLZAcwG5LqATCABs4IAPzNkAVzoAjCAl4BfcuVCQoAYQAWWIfwzY8hEvCSpDuAlABkZPlQDGOITgTNW7LstWOR+QjMUYHtqKGcCNilHYDcAChxMK3xmIIsk4wBKewcoFRVyPzgArV19KAgAD2AUfkDEYNDqCM9o2IQEjIJmHT0DLvxsijCw-ClIDsSjAkzeEebjEIYAuE5oEgADABJSKeSAOloGJSgsQh29433nVwQlDbnqfKA) From 49443d4f6e0621591ac1d37f021c474e1decc888 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 30 Nov 2021 14:17:51 +0000 Subject: [PATCH 170/174] Update TS-MIGRATION.md --- doc/TS-MIGRATION.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/doc/TS-MIGRATION.md b/doc/TS-MIGRATION.md index 006b3077..d7ebc7f4 100644 --- a/doc/TS-MIGRATION.md +++ b/doc/TS-MIGRATION.md @@ -1,10 +1,4 @@ -# Typescript migration - -## Introduce `abstract` & `override` - - - find all methods and getters that throw or are empty in base classes and turn into abstract method or if all methods are abstract, into an interface. - - change child impls to not call super.method and to add override - - don't allow implicit override in ts config +# Typescript style guide ## Use `type` rather than `interface` for named parameters and POJO return values. From 581ef47c78db67fed09fb1f314acaad8f3610feb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 30 Nov 2021 16:53:59 +0100 Subject: [PATCH 171/174] fix conflicting sortedIndex declaration --- src/observable/list/SortedArray.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/observable/list/SortedArray.ts b/src/observable/list/SortedArray.ts index abbfa481..c85cca27 100644 --- a/src/observable/list/SortedArray.ts +++ b/src/observable/list/SortedArray.ts @@ -18,8 +18,6 @@ import {BaseObservableList} from "./BaseObservableList"; import {sortedIndex} from "../../utils/sortedIndex"; import {findAndUpdateInArray} from "./common"; -declare function sortedIndex(array: T[], value: T, comparator: (left: T, right: T) => number): number; - export class SortedArray extends BaseObservableList { private _comparator: (left: T, right: T) => number; private _items: T[] = []; From de8995fa7ed320b3ed6cc826ae55766be7268e39 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 30 Nov 2021 16:58:56 +0100 Subject: [PATCH 172/174] fix handlers in test missing methods, now that observable list is typed --- src/platform/web/ui/general/ListRange.ts | 61 ++++++++++++------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/platform/web/ui/general/ListRange.ts b/src/platform/web/ui/general/ListRange.ts index 4f62ba21..66ee8dfb 100644 --- a/src/platform/web/ui/general/ListRange.ts +++ b/src/platform/web/ui/general/ListRange.ts @@ -15,6 +15,7 @@ limitations under the License. */ import {Range, RangeZone} from "./Range"; +import {defaultObserverWith} from "../../../../observable/list/BaseObservableList"; function skipOnIterator(it: Iterator, pos: number): boolean { let i = 0; @@ -268,7 +269,7 @@ export function tests() { const list = new ObservableArray(["b", "c", "d", "e"]); const range = new ListRange(1, 3, list.length); let added = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onAdd(idx, value) { added = true; const result = range.queryAdd(idx, value, list); @@ -280,7 +281,7 @@ export function tests() { newRange: new ListRange(1, 3, 5) }); } - }); + })); list.insert(0, "a"); assert(added); }, @@ -288,7 +289,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "d", "e"]); const range = new ListRange(1, 3, list.length); let added = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onAdd(idx, value) { added = true; const result = range.queryAdd(idx, value, list); @@ -300,7 +301,7 @@ export function tests() { newRange: new ListRange(1, 3, 5) }); } - }); + })); list.insert(2, "c"); assert(added); }, @@ -308,7 +309,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d"]); const range = new ListRange(1, 3, list.length); let added = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onAdd(idx, value) { added = true; const result = range.queryAdd(idx, value, list); @@ -317,7 +318,7 @@ export function tests() { newRange: new ListRange(1, 3, 5) }); } - }); + })); list.insert(4, "e"); assert(added); }, @@ -326,7 +327,7 @@ export function tests() { const viewportItemCount = 4; const range = new ListRange(0, 3, list.length, viewportItemCount); let added = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onAdd(idx, value) { added = true; const result = range.queryAdd(idx, value, list); @@ -337,7 +338,7 @@ export function tests() { value: "c" }); } - }); + })); list.insert(2, "c"); assert(added); }, @@ -345,7 +346,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(1, 3, list.length); let removed = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onRemove(idx) { removed = true; const result = range.queryRemove(idx, list); @@ -357,7 +358,7 @@ export function tests() { newRange: new ListRange(1, 3, 4) }); } - }); + })); list.remove(0); assert(removed); }, @@ -365,7 +366,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(1, 3, list.length); let removed = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onRemove(idx) { removed = true; const result = range.queryRemove(idx, list); @@ -378,7 +379,7 @@ export function tests() { }); assert.equal(list.length, 4); } - }); + })); list.remove(2); assert(removed); }, @@ -386,7 +387,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(1, 3, list.length); let removed = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onRemove(idx) { removed = true; const result = range.queryRemove(idx, list); @@ -395,7 +396,7 @@ export function tests() { newRange: new ListRange(1, 3, 4) }); } - }); + })); list.remove(3); assert(removed); }, @@ -403,7 +404,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c"]); const range = new ListRange(1, 3, list.length); let removed = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onRemove(idx) { removed = true; const result = range.queryRemove(idx, list); @@ -415,7 +416,7 @@ export function tests() { value: "a" }); } - }); + })); list.remove(2); assert(removed); }, @@ -423,7 +424,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c"]); const range = new ListRange(0, 3, list.length); let removed = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onRemove(idx) { removed = true; const result = range.queryRemove(idx, list); @@ -433,7 +434,7 @@ export function tests() { removeIdx: 2, }); } - }); + })); list.remove(2); assert(removed); }, @@ -441,7 +442,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(1, 4, list.length); let moved = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onMove(fromIdx, toIdx, value) { moved = true; const result = range.queryMove(fromIdx, toIdx, value, list); @@ -451,7 +452,7 @@ export function tests() { toIdx: 3 }); } - }); + })); list.move(2, 3); assert(moved); }, @@ -459,7 +460,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(2, 5, list.length); let moved = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onMove(fromIdx, toIdx, value) { moved = true; const result = range.queryMove(fromIdx, toIdx, value, list); @@ -470,7 +471,7 @@ export function tests() { value: "a" }); } - }); + })); list.move(0, 3); // move "a" to after "d" assert(moved); }, @@ -478,7 +479,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(0, 3, list.length); let moved = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onMove(fromIdx, toIdx, value) { moved = true; const result = range.queryMove(fromIdx, toIdx, value, list); @@ -489,7 +490,7 @@ export function tests() { value: "e" }); } - }); + })); list.move(4, 1); // move "e" to before "b" assert(moved); }, @@ -497,7 +498,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(0, 3, list.length); let moved = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onMove(fromIdx, toIdx, value) { moved = true; const result = range.queryMove(fromIdx, toIdx, value, list); @@ -508,7 +509,7 @@ export function tests() { value: "d" }); } - }); + })); list.move(1, 3); // move "b" to after "d" assert(moved); }, @@ -516,7 +517,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(2, 5, list.length); let moved = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onMove(fromIdx, toIdx, value) { moved = true; const result = range.queryMove(fromIdx, toIdx, value, list); @@ -527,7 +528,7 @@ export function tests() { value: "b" }); } - }); + })); list.move(3, 0); // move "d" to before "a" assert(moved); }, @@ -535,7 +536,7 @@ export function tests() { const list = new ObservableArray(["a", "b", "c", "d", "e"]); const range = new ListRange(1, 4, list.length); let moved = false; - list.subscribe({ + list.subscribe(defaultObserverWith({ onMove(fromIdx, toIdx, value) { moved = true; const result = range.queryMove(fromIdx, toIdx, value, list); @@ -546,7 +547,7 @@ export function tests() { value: "e" }); } - }); + })); list.move(0, 4); // move "a" to after "e" assert(moved); }, From 8c3ae57497eed173b2a469da61f0938410ca5c2f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 30 Nov 2021 17:05:39 +0100 Subject: [PATCH 173/174] fix Iterator vs IterableIterator confusion --- src/observable/list/BaseObservableList.ts | 4 ++-- src/platform/web/ui/general/Range.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/observable/list/BaseObservableList.ts b/src/observable/list/BaseObservableList.ts index 6d8bd1af..d103eb64 100644 --- a/src/observable/list/BaseObservableList.ts +++ b/src/observable/list/BaseObservableList.ts @@ -35,7 +35,7 @@ export function defaultObserverWith(overrides: { [key in keyof IListObserver< return Object.assign(defaults, overrides); } -export abstract class BaseObservableList extends BaseObservable> { +export abstract class BaseObservableList extends BaseObservable> implements Iterable { emitReset() { for(let h of this._handlers) { h.onReset(this); @@ -69,6 +69,6 @@ export abstract class BaseObservableList extends BaseObservable; abstract get length(): number; } diff --git a/src/platform/web/ui/general/Range.ts b/src/platform/web/ui/general/Range.ts index 16dc0a33..a3ca54c0 100644 --- a/src/platform/web/ui/general/Range.ts +++ b/src/platform/web/ui/general/Range.ts @@ -43,7 +43,7 @@ export class Range { return range.start < this.end && this.start < range.end; } - forEachInIterator(it: IterableIterator, callback: ((T, i: number) => void)) { + forEachInIterator(it: Iterator, callback: ((T, i: number) => void)) { let i = 0; for (i = 0; i < this.start; i += 1) { it.next(); From 85385a0aa7c9e725f16ff192893ddf34032f7fe6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 1 Dec 2021 09:43:58 +0100 Subject: [PATCH 174/174] adjust path --- src/platform/web/ui/general/ListRange.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/general/ListRange.ts b/src/platform/web/ui/general/ListRange.ts index 66ee8dfb..23e0ab2e 100644 --- a/src/platform/web/ui/general/ListRange.ts +++ b/src/platform/web/ui/general/ListRange.ts @@ -215,7 +215,7 @@ export class ListRange extends Range { } } -import {ObservableArray} from "../../../../observable/list/ObservableArray.js"; +import {ObservableArray} from "../../../../observable/list/ObservableArray"; export function tests() { return {