diff --git a/doc/TS-MIGRATION.md b/doc/TS-MIGRATION.md index 7cb5bb34..d7ebc7f4 100644 --- a/doc/TS-MIGRATION.md +++ b/doc/TS-MIGRATION.md @@ -1,7 +1,9 @@ -# Typescript migration - -## Introduce `abstract` & `override` +# Typescript style guide - - 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) diff --git a/package.json b/package.json index 1aea67f9..097c593e 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/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/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/ReactionsViewModel.js b/src/domain/session/room/timeline/ReactionsViewModel.js index 3a47aaa0..ad29c01a 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"; 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/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js index b3696fad..54ab5ddd 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. @@ -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/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/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/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/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..ab9474b0 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/matrix/Session.js b/src/matrix/Session.js index 37fd90ad..17c0cf94 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -19,7 +19,7 @@ import {Room} from "./room/Room.js"; import {ArchivedRoom} from "./room/ArchivedRoom.js"; import {RoomStatus} from "./room/RoomStatus.js"; import {Invite} from "./room/Invite.js"; -import {Pusher} from "./push/Pusher.js"; +import {Pusher} from "./push/Pusher"; import { ObservableMap } from "../observable/index.js"; import {User} from "./User.js"; import {DeviceMessageHandler} from "./DeviceMessageHandler.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/matrix/SessionContainer.js b/src/matrix/SessionContainer.js index fd523ac7..a10056ac 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"; @@ -27,9 +27,9 @@ import {RequestScheduler} from "./net/RequestScheduler.js"; // import {Sync, SyncStatus} from "./Sync.js"; import {Sync3, SyncStatus} from "./Sync3"; import {Session} from "./Session.js"; -import {PasswordLoginMethod} from "./login/PasswordLoginMethod.js"; -import {TokenLoginMethod} from "./login/TokenLoginMethod.js"; -import {SSOLoginHelper} from "./login/SSOLoginHelper.js"; +import {PasswordLoginMethod} from "./login/PasswordLoginMethod"; +import {TokenLoginMethod} from "./login/TokenLoginMethod"; +import {SSOLoginHelper} from "./login/SSOLoginHelper"; import {getDehydratedDevice} from "./e2ee/Dehydration.js"; export const LoadStatus = createEnum( 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/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/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/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/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/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/login/LoginMethod.js b/src/matrix/login/LoginMethod.ts similarity index 61% rename from src/matrix/login/LoginMethod.js rename to src/matrix/login/LoginMethod.ts index ece18871..2b2a8ff3 100644 --- a/src/matrix/login/LoginMethod.js +++ b/src/matrix/login/LoginMethod.ts @@ -14,17 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -export class LoginMethod { - constructor({homeserver}) { - this.homeserver = homeserver; - } +import type {ILogItem} from "../../logging/types"; +import type {HomeServerApi} from "../net/HomeServerApi.js"; - // eslint-disable-next-line no-unused-vars - async login(hsApi, deviceName, log) { - /* - Regardless of the login method, SessionContainer.startWithLogin() - can do SomeLoginMethod.login() - */ - throw("Not Implemented"); - } +export interface ILoginMethod { + homeserver: string; + login(hsApi: HomeServerApi, deviceName: string, log: ILogItem): Promise>; } diff --git a/src/matrix/login/PasswordLoginMethod.js b/src/matrix/login/PasswordLoginMethod.js deleted file mode 100644 index 5c90ccf8..00000000 --- a/src/matrix/login/PasswordLoginMethod.js +++ /dev/null @@ -1,29 +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 {LoginMethod} from "./LoginMethod.js"; - -export class PasswordLoginMethod extends LoginMethod { - constructor(options) { - super(options); - this.username = options.username; - this.password = options.password; - } - - async login(hsApi, deviceName, log) { - return await hsApi.passwordLogin(this.username, this.password, deviceName, {log}).response(); - } -} diff --git a/src/matrix/login/PasswordLoginMethod.ts b/src/matrix/login/PasswordLoginMethod.ts new file mode 100644 index 00000000..65e96ff0 --- /dev/null +++ b/src/matrix/login/PasswordLoginMethod.ts @@ -0,0 +1,35 @@ +/* +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 {ILogItem} from "../../logging/types"; +import {ILoginMethod} from "./LoginMethod"; +import {HomeServerApi} from "../net/HomeServerApi.js"; + +export class PasswordLoginMethod implements ILoginMethod { + private readonly _username: string; + private readonly _password: string; + public readonly homeserver: string; + + constructor({username, password, homeserver}: {username: string, password: string, homeserver: string}) { + this._username = username; + this._password = password; + this.homeserver = homeserver; + } + + async login(hsApi: HomeServerApi, deviceName: string, log: ILogItem): Promise> { + return await hsApi.passwordLogin(this._username, this._password, deviceName, {log}).response(); + } +} diff --git a/src/matrix/login/SSOLoginHelper.js b/src/matrix/login/SSOLoginHelper.ts similarity index 80% rename from src/matrix/login/SSOLoginHelper.js rename to src/matrix/login/SSOLoginHelper.ts index a15c8ef9..0fe3d6b8 100644 --- a/src/matrix/login/SSOLoginHelper.js +++ b/src/matrix/login/SSOLoginHelper.ts @@ -15,13 +15,15 @@ limitations under the License. */ export class SSOLoginHelper{ - constructor(homeserver) { + private _homeserver: string; + + constructor(homeserver: string) { this._homeserver = homeserver; } - get homeserver() { return this._homeserver; } + get homeserver(): string { return this._homeserver; } - createSSORedirectURL(returnURL) { + createSSORedirectURL(returnURL: string): string { return `${this._homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${returnURL}`; } } diff --git a/src/matrix/login/TokenLoginMethod.js b/src/matrix/login/TokenLoginMethod.ts similarity index 57% rename from src/matrix/login/TokenLoginMethod.js rename to src/matrix/login/TokenLoginMethod.ts index e55cedcf..4f1e3cc7 100644 --- a/src/matrix/login/TokenLoginMethod.js +++ b/src/matrix/login/TokenLoginMethod.ts @@ -14,16 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {LoginMethod} from "./LoginMethod.js"; import {makeTxnId} from "../common.js"; +import {ILogItem} from "../../logging/types"; +import {ILoginMethod} from "./LoginMethod"; +import {HomeServerApi} from "../net/HomeServerApi.js"; -export class TokenLoginMethod extends LoginMethod { - constructor(options) { - super(options); - this._loginToken = options.loginToken; +export class TokenLoginMethod implements ILoginMethod { + private readonly _loginToken: string; + public readonly homeserver: string; + + constructor({ homeserver, loginToken }: { homeserver: string, loginToken: string}) { + this.homeserver = homeserver; + this._loginToken = loginToken; } - async login(hsApi, deviceName, log) { + async login(hsApi: HomeServerApi, deviceName: string, log: ILogItem): Promise> { return await hsApi.tokenLogin(this._loginToken, makeTxnId(), deviceName, {log}).response(); } } 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/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/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/push/Pusher.js b/src/matrix/push/Pusher.js deleted file mode 100644 index 99baeae6..00000000 --- a/src/matrix/push/Pusher.js +++ /dev/null @@ -1,50 +0,0 @@ -export class Pusher { - constructor(description) { - this._description = description; - } - - static httpPusher(host, appId, pushkey, data) { - return new Pusher({ - kind: "http", - append: true, // as pushkeys are shared between multiple users on one origin - data: Object.assign({}, data, {url: host + "/_matrix/push/v1/notify"}), - pushkey, - app_id: appId, - app_display_name: "Hydrogen", - device_display_name: "Hydrogen", - lang: "en" - }); - } - - static createDefaultPayload(sessionId) { - return {session_id: sessionId}; - } - - async enable(hsApi, log) { - try { - log.set("endpoint", new URL(this._description.data.endpoint).host); - } catch { - log.set("endpoint", null); - } - await hsApi.setPusher(this._description, {log}).response(); - } - - async disable(hsApi, log) { - const deleteDescription = Object.assign({}, this._description, {kind: null}); - await hsApi.setPusher(deleteDescription, {log}).response(); - } - - serialize() { - return this._description; - } - - equals(pusher) { - if (this._description.app_id !== pusher._description.app_id) { - return false; - } - if (this._description.pushkey !== pusher._description.pushkey) { - return false; - } - return JSON.stringify(this._description.data) === JSON.stringify(pusher._description.data); - } -} diff --git a/src/matrix/push/Pusher.ts b/src/matrix/push/Pusher.ts new file mode 100644 index 00000000..9f970322 --- /dev/null +++ b/src/matrix/push/Pusher.ts @@ -0,0 +1,90 @@ +/* +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 type {HomeServerApi} from "../net/HomeServerApi.js"; +import type {ILogItem} from "../../logging/types"; + +export interface IPusherDescription { + kind: "http" | "email" | "null"; + lang: string; + device_display_name: string; + app_display_name: string; + app_id: string; + pushkey: string; + data: IPusherData; + append?: boolean; + profile_tag?: string; +} + +interface IPusherData { + format?: string; + url?: string; + endpoint?: PushSubscriptionJSON["endpoint"]; + keys?: PushSubscriptionJSON["keys"]; +} + +export class Pusher { + private readonly _description: IPusherDescription; + + constructor(description: IPusherDescription) { + this._description = description; + } + + static httpPusher(host: string, appId: string, pushkey: string, data: IPusherData): Pusher { + return new Pusher({ + kind: "http", + append: true, // as pushkeys are shared between multiple users on one origin + data: Object.assign({}, data, {url: host + "/_matrix/push/v1/notify"}), + pushkey, + app_id: appId, + app_display_name: "Hydrogen", + device_display_name: "Hydrogen", + lang: "en" + }); + } + + static createDefaultPayload(sessionId: string): {session_id: string} { + return {session_id: sessionId}; + } + + async enable(hsApi: HomeServerApi, log: ILogItem): Promise { + try { + log.set("endpoint", new URL(this._description.data.endpoint!).host); + } catch { + log.set("endpoint", null); + } + await hsApi.setPusher(this._description, {log}).response(); + } + + async disable(hsApi: HomeServerApi, log: ILogItem): Promise { + const deleteDescription = Object.assign({}, this._description, {kind: null}); + await hsApi.setPusher(deleteDescription, {log}).response(); + } + + serialize(): IPusherDescription { + return this._description; + } + + equals(pusher): boolean { + if (this._description.app_id !== pusher._description.app_id) { + return false; + } + if (this._description.pushkey !== pusher._description.pushkey) { + return false; + } + return JSON.stringify(this._description.data) === JSON.stringify(pusher._description.data); + } +} 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/matrix/room/sending/PendingEvent.js b/src/matrix/room/sending/PendingEvent.js index 01874178..d4c10704 100644 --- a/src/matrix/room/sending/PendingEvent.js +++ b/src/matrix/room/sending/PendingEvent.js @@ -13,8 +13,8 @@ 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 {AbortError} from "../../../utils/error.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/room/sending/SendQueue.js b/src/matrix/room/sending/SendQueue.js index 896100c7..791ed854 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/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/matrix/sessioninfo/localstorage/SessionInfoStorage.js b/src/matrix/sessioninfo/localstorage/SessionInfoStorage.ts similarity index 64% rename from src/matrix/sessioninfo/localstorage/SessionInfoStorage.js rename to src/matrix/sessioninfo/localstorage/SessionInfoStorage.ts index ce795916..ebe575f6 100644 --- a/src/matrix/sessioninfo/localstorage/SessionInfoStorage.js +++ b/src/matrix/sessioninfo/localstorage/SessionInfoStorage.ts @@ -14,12 +14,33 @@ See the License for the specific language governing permissions and limitations under the License. */ -export class SessionInfoStorage { - constructor(name) { +interface ISessionInfo { + id: string; + deviceId: string; + userId: string; + homeserver: string; + homeServer: string; // deprecate this over time + accessToken: string; + lastUsed: number; +} + +// todo: this should probably be in platform/types? +interface ISessionInfoStorage { + getAll(): Promise; + updateLastUsed(id: string, timestamp: number): Promise; + get(id: string): Promise; + add(sessionInfo: ISessionInfo): Promise; + delete(sessionId: string): Promise; +} + +export class SessionInfoStorage implements ISessionInfoStorage { + private readonly _name: string; + + constructor(name: string) { this._name = name; } - getAll() { + getAll(): Promise { const sessionsJson = localStorage.getItem(this._name); if (sessionsJson) { const sessions = JSON.parse(sessionsJson); @@ -30,7 +51,7 @@ export class SessionInfoStorage { return Promise.resolve([]); } - async updateLastUsed(id, timestamp) { + async updateLastUsed(id: string, timestamp: number): Promise { const sessions = await this.getAll(); if (sessions) { const session = sessions.find(session => session.id === id); @@ -41,20 +62,20 @@ export class SessionInfoStorage { } } - async get(id) { + async get(id: string): Promise { const sessions = await this.getAll(); if (sessions) { return sessions.find(session => session.id === id); } } - async add(sessionInfo) { + async add(sessionInfo: ISessionInfo): Promise { const sessions = await this.getAll(); sessions.push(sessionInfo); localStorage.setItem(this._name, JSON.stringify(sessions)); } - async delete(sessionId) { + async delete(sessionId: string): Promise { let sessions = await this.getAll(); sessions = sessions.filter(s => s.id !== sessionId); localStorage.setItem(this._name, JSON.stringify(sessions)); 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`; 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/observable/index.js b/src/observable/index.js index 47b68e91..4d7f18a3 100644 --- a/src/observable/index.js +++ b/src/observable/index.js @@ -20,11 +20,11 @@ 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 { SortedArray } from "./list/SortedArray.js"; -export { MappedList } from "./list/MappedList.js"; -export { AsyncMappedList } from "./list/AsyncMappedList.js"; -export { ConcatList } from "./list/ConcatList.js"; +export { ObservableArray } from "./list/ObservableArray"; +export { SortedArray } from "./list/SortedArray"; +export { MappedList } from "./list/MappedList"; +export { AsyncMappedList } from "./list/AsyncMappedList"; +export { ConcatList } from "./list/ConcatList"; export { ObservableMap } from "./map/ObservableMap.js"; // avoid circular dependency between these classes diff --git a/src/observable/list/AsyncMappedList.js b/src/observable/list/AsyncMappedList.ts similarity index 70% rename from src/observable/list/AsyncMappedList.js rename to src/observable/list/AsyncMappedList.ts index 9af1f95a..0a919cdc 100644 --- a/src/observable/list/AsyncMappedList.js +++ 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,122 +34,112 @@ 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); } } -import {ObservableArray} from "./ObservableArray.js"; +import {ObservableArray} from "./ObservableArray"; import {ListObserver} from "../../mocks/ListObserver.js"; 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)}; diff --git a/src/observable/list/BaseMappedList.ts b/src/observable/list/BaseMappedList.ts index f2203d34..4e3d05e0 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; - _updater: Updater; + _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(); } diff --git a/src/observable/list/BaseObservableList.ts b/src/observable/list/BaseObservableList.ts index 9ba6559a..d103eb64 100644 --- a/src/observable/list/BaseObservableList.ts +++ b/src/observable/list/BaseObservableList.ts @@ -24,7 +24,18 @@ export interface IListObserver { onMove(from: number, to: number, value: T, list: BaseObservableList): void } -export abstract class BaseObservableList extends BaseObservable> { +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> implements Iterable { emitReset() { for(let h of this._handlers) { h.onReset(this); @@ -38,7 +49,7 @@ export abstract class BaseObservableList extends BaseObservable extends BaseObservable; + abstract [Symbol.iterator](): Iterator; abstract get length(): number; } diff --git a/src/observable/list/ConcatList.js b/src/observable/list/ConcatList.ts similarity index 80% rename from src/observable/list/ConcatList.js rename to src/observable/list/ConcatList.ts index e87ccb60..5822468a 100644 --- a/src/observable/list/ConcatList.js +++ 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; @@ -104,7 +106,8 @@ export class ConcatList extends BaseObservableList { } } -import {ObservableArray} from "./ObservableArray.js"; +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); }, diff --git a/src/observable/list/MappedList.js b/src/observable/list/MappedList.ts similarity index 80% rename from src/observable/list/MappedList.js rename to src/observable/list/MappedList.ts index c799fcff..ebb418d3 100644 --- a/src/observable/list/MappedList.js +++ 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.js"; +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); }, diff --git a/src/observable/list/ObservableArray.js b/src/observable/list/ObservableArray.ts similarity index 78% rename from src/observable/list/ObservableArray.js rename to src/observable/list/ObservableArray.ts index afdaba37..0771d0f6 100644 --- a/src/observable/list/ObservableArray.js +++ b/src/observable/list/ObservableArray.ts @@ -16,35 +16,37 @@ 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); } - move(fromIdx, toIdx) { + move(fromIdx: number, toIdx: number): void { if (fromIdx < this._items.length && toIdx < this._items.length) { const [item] = this._items.splice(fromIdx, 1); this._items.splice(toIdx, 0, item); @@ -52,24 +54,24 @@ export class ObservableArray extends BaseObservableList { } } - 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; } diff --git a/src/observable/list/SortedArray.js b/src/observable/list/SortedArray.ts similarity index 80% rename from src/observable/list/SortedArray.js rename to src/observable/list/SortedArray.ts index 874d1b04..c85cca27 100644 --- a/src/observable/list/SortedArray.js +++ b/src/observable/list/SortedArray.ts @@ -15,21 +15,23 @@ 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 { - constructor(comparator) { +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 +44,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 +58,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 +66,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 +75,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 +83,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 +94,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 +118,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 +150,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 +158,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 +167,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); diff --git a/src/observable/list/SortedMapList.js b/src/observable/list/SortedMapList.js index 6b486bd5..7fffec5d 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/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/platform/web/Platform.js b/src/platform/web/Platform.js index f6cd2895..f0149212 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -17,7 +17,7 @@ limitations under the License. import {createFetchRequest} from "./dom/request/fetch.js"; import {xhrRequest} from "./dom/request/xhr.js"; import {StorageFactory} from "../../matrix/storage/idb/StorageFactory"; -import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionInfoStorage.js"; +import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionInfoStorage"; import {SettingsStorage} from "./dom/SettingsStorage.js"; import {Encoding} from "./utils/Encoding.js"; import {OlmWorker} from "../../matrix/e2ee/OlmWorker.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"; @@ -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); + } + }; +} 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/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/platform/web/ui/general/ListRange.ts b/src/platform/web/ui/general/ListRange.ts index 4f62ba21..23e0ab2e 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; @@ -214,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 { @@ -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); }, 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(); 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/Disposables.js b/src/utils/Disposables.ts similarity index 66% rename from src/utils/Disposables.js rename to src/utils/Disposables.ts index b63cba85..19a5983c 100644 --- a/src/utils/Disposables.js +++ b/src/utils/Disposables.ts @@ -14,7 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -function disposeValue(value) { +export interface IDisposable { + dispose(): void; +} + +type Disposable = IDisposable | (() => void); + +function disposeValue(value: Disposable): void { if (typeof value === "function") { value(); } else { @@ -22,16 +28,14 @@ function disposeValue(value) { } } -function isDisposable(value) { +function isDisposable(value: Disposable): boolean { return value && (typeof value === "function" || typeof value.dispose === "function"); } export class Disposables { - constructor() { - this._disposables = []; - } + private _disposables: Disposable[] | null = []; - track(disposable) { + track(disposable: Disposable): Disposable { if (!isDisposable(disposable)) { throw new Error("Not a disposable"); } @@ -40,19 +44,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): null { + if (this.isDisposed) { + console.warn("Disposables already disposed, cannot untrack"); + return null; + } + 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); @@ -61,17 +69,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); diff --git a/src/utils/Lock.js b/src/utils/Lock.ts similarity index 81% rename from src/utils/Lock.js rename to src/utils/Lock.ts index 8cfc733f..238d88f9 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; + private _resolve?: (() => void); - tryTake() { + tryTake(): boolean { if (!this._promise) { this._promise = new Promise(resolve => { this._resolve = resolve; @@ -30,36 +28,36 @@ 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; + this._promise = undefined; const resolve = this._resolve; - this._resolve = null; + this._resolve = undefined; resolve(); } } - released() { + released(): Promise | undefined { 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.ts similarity index 90% rename from src/utils/LockMap.js rename to src/utils/LockMap.ts index a73dee4a..4c7cba13 100644 --- a/src/utils/LockMap.js +++ b/src/utils/LockMap.ts @@ -14,14 +14,12 @@ 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() { - this._map = new Map(); - } +export class LockMap { + private readonly _map: Map = new Map(); - async takeLock(key) { + async takeLock(key: T): 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); }, 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(); diff --git a/src/utils/crypto/hkdf.js b/src/utils/crypto/hkdf.ts similarity index 82% rename from src/utils/crypto/hkdf.js rename to src/utils/crypto/hkdf.ts index d46dc496..44f66029 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: "SHA-256" | "SHA-512", length: number): Promise { length = length / 8; const len = cryptoDriver.digestSize(hash); diff --git a/src/utils/crypto/pbkdf2.js b/src/utils/crypto/pbkdf2.ts similarity index 82% rename from src/utils/crypto/pbkdf2.js rename to src/utils/crypto/pbkdf2.ts index 5a239791..a9108377 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: "SHA-256" | "SHA-512", 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)); 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); 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"; } } 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..f611c79e 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 { 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); @@ -25,4 +26,5 @@ export function formatSize(size, decimals = 2) { case 3: return `${formattedSize} GB`; } } + return ""; } 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..b5d9b524 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): void { if (src) { for (const [key, value] of src.entries()) { dst.set(key, value); 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; diff --git a/src/utils/timeout.js b/src/utils/timeout.ts similarity index 91% rename from src/utils/timeout.js rename to src/utils/timeout.ts index 6bfc0d7e..f0460047 100644 --- a/src/utils/timeout.js +++ b/src/utils/timeout.ts @@ -16,9 +16,12 @@ 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; -export function abortOnTimeout(createTimeout, timeoutAmount, requestResult, responsePromise) { +export function abortOnTimeout(createTimeout: TimeoutCreator, timeoutAmount: number, requestResult: IAbortable, responsePromise: Promise) { const timeout = createTimeout(timeoutAmount); // abort request if timeout finishes first let timedOut = false; 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"