diff --git a/package.json b/package.json index 0105b5146..9944fbd28 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js", "gendoc": "jsdoc -c jsdoc.json -P package.json", "lint": "yarn lint:types && yarn lint:js", - "lint:js": "eslint --max-warnings 7 src spec", + "lint:js": "eslint --max-warnings 4 src spec", "lint:js-fix": "eslint --fix src spec", "lint:types": "tsc --noEmit", "test": "jest", diff --git a/spec/integ/matrix-client-retrying.spec.ts b/spec/integ/matrix-client-retrying.spec.ts index 99214ca62..d0335668f 100644 --- a/spec/integ/matrix-client-retrying.spec.ts +++ b/spec/integ/matrix-client-retrying.spec.ts @@ -4,13 +4,13 @@ import { Room } from "../../src/models/room"; import { TestClient } from "../TestClient"; describe("MatrixClient retrying", function() { - let client = null; - let httpBackend = null; + let client: TestClient = null; + let httpBackend: TestClient["httpBackend"] = null; let scheduler; const userId = "@alice:localhost"; const accessToken = "aseukfgwef"; const roomId = "!room:here"; - let room; + let room: Room; beforeEach(function() { scheduler = new MatrixScheduler(); @@ -53,10 +53,10 @@ describe("MatrixClient retrying", function() { const p1 = client.sendMessage(roomId, { "msgtype": "m.text", "body": "m1", - }).then(function(ev) { + }).then(function() { // we expect the first message to fail throw new Error('Message 1 unexpectedly sent successfully'); - }, (e) => { + }, () => { // this is expected }); @@ -78,7 +78,7 @@ describe("MatrixClient retrying", function() { expect(ev2.status).toEqual(EventStatus.SENDING); // the first message should get sent, and the second should get queued - httpBackend.when("PUT", "/send/m.room.message/").check(function(rq) { + httpBackend.when("PUT", "/send/m.room.message/").check(function() { // ev2 should now have been queued expect(ev2.status).toEqual(EventStatus.QUEUED); diff --git a/spec/unit/models/MSC3089TreeSpace.spec.ts b/spec/unit/models/MSC3089TreeSpace.spec.ts index 61d1daace..9c3a93e53 100644 --- a/spec/unit/models/MSC3089TreeSpace.spec.ts +++ b/spec/unit/models/MSC3089TreeSpace.spec.ts @@ -1018,7 +1018,7 @@ describe("MSC3089TreeSpace", () => { it('should return falsy for unknown files', () => { const fileEventId = "$file"; room.currentState = { - getStateEvents: (eventType: string, stateKey?: string) => { + getStateEvents: (eventType: string, stateKey?: string): MatrixEvent[] | MatrixEvent | null => { expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable expect(stateKey).toEqual(fileEventId); return null; diff --git a/spec/unit/utils.spec.ts b/spec/unit/utils.spec.ts index 12a83b235..61d11b1cf 100644 --- a/spec/unit/utils.spec.ts +++ b/spec/unit/utils.spec.ts @@ -111,10 +111,10 @@ describe("utils", function() { describe("deepCompare", function() { const assert = { - isTrue: function(x) { + isTrue: function(x: any) { expect(x).toBe(true); }, - isFalse: function(x) { + isFalse: function(x: any) { expect(x).toBe(false); }, }; @@ -176,10 +176,10 @@ describe("utils", function() { // no two different function is equal really, they capture their // context variables so even if they have same toString(), they // won't have same functionality - const func = function(x) { + const func = function() { return true; }; - const func2 = function(x) { + const func2 = function() { return true; }; assert.isTrue(utils.deepCompare(func, func)); @@ -189,66 +189,6 @@ describe("utils", function() { }); }); - describe("extend", function() { - const SOURCE = { "prop2": 1, "string2": "x", "newprop": "new" }; - - it("should extend", function() { - const target = { - "prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo", - }; - const merged = { - "prop1": 5, "prop2": 1, "string1": "baz", "string2": "x", - "newprop": "new", - }; - const sourceOrig = JSON.stringify(SOURCE); - - utils.extend(target, SOURCE); - expect(JSON.stringify(target)).toEqual(JSON.stringify(merged)); - - // check the originial wasn't modified - expect(JSON.stringify(SOURCE)).toEqual(sourceOrig); - }); - - it("should ignore null", function() { - const target = { - "prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo", - }; - const merged = { - "prop1": 5, "prop2": 1, "string1": "baz", "string2": "x", - "newprop": "new", - }; - const sourceOrig = JSON.stringify(SOURCE); - - utils.extend(target, null, SOURCE); - expect(JSON.stringify(target)).toEqual(JSON.stringify(merged)); - - // check the originial wasn't modified - expect(JSON.stringify(SOURCE)).toEqual(sourceOrig); - }); - - it("should handle properties created with defineProperties", function() { - const source = Object.defineProperties({}, { - "enumerableProp": { - get: function() { - return true; - }, - enumerable: true, - }, - "nonenumerableProp": { - get: function() { - return true; - }, - }, - }); - - // TODO: Fix type - const target: any = {}; - utils.extend(target, source); - expect(target.enumerableProp).toBe(true); - expect(target.nonenumerableProp).toBe(undefined); - }); - }); - describe("chunkPromises", function() { it("should execute promises in chunks", async function() { let promiseCount = 0; @@ -273,7 +213,7 @@ describe("utils", function() { it('should retry', async () => { let count = 0; const val = {}; - const fn = (attempt) => { + const fn = (attempt: any) => { count++; // If this expectation fails then it can appear as a Jest Timeout due to @@ -480,7 +420,7 @@ describe("utils", function() { }, [72]: "test", }; - const output = [ + const output: any = [ ["72", "test"], ["a", 42], ["b", [ diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 9e12db20f..bcded3fb7 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -17,6 +17,7 @@ limitations under the License. import { TestClient } from '../../TestClient'; import { MatrixCall, CallErrorCode, CallEvent } from '../../../src/webrtc/call'; import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from '../../../src/webrtc/callEventTypes'; +import { RoomMember } from "../../../src"; const DUMMY_SDP = ( "v=0\r\n" + @@ -85,7 +86,7 @@ class MockRTCPeerConnection { class MockMediaStream { constructor( - public id, + public id: string, ) {} getTracks() { return []; } @@ -362,7 +363,7 @@ describe('Call', function() { await callPromise; call.getOpponentMember = () => { - return { userId: "@bob:bar.uk" }; + return { userId: "@bob:bar.uk" } as RoomMember; }; await call.onAnswerReceived({ diff --git a/src/ReEmitter.ts b/src/ReEmitter.ts index b1060cbb8..03c13dd60 100644 --- a/src/ReEmitter.ts +++ b/src/ReEmitter.ts @@ -30,7 +30,7 @@ export class ReEmitter { // We include the source as the last argument for event handlers which may need it, // such as read receipt listeners on the client class which won't have the context // of the room. - const forSource = (...args) => { + const forSource = (...args: any[]) => { // EventEmitter special cases 'error' to make the emit function throw if no // handler is attached, which sort of makes sense for making sure that something // handles an error, but for re-emitting, there could be a listener on the original diff --git a/src/client.ts b/src/client.ts index ecb684cfc..2244632f6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1423,7 +1423,7 @@ export class MatrixClient extends EventEmitter { // We swallow errors because we need a default object anyhow return this.http.authedRequest( undefined, "GET", "/capabilities", - ).catch((e) => { + ).catch((e: Error) => { logger.error(e); return null; // otherwise consume the error }).then((r) => { @@ -3106,7 +3106,7 @@ export class MatrixClient extends EventEmitter { * data event. * @return {module:http-api.MatrixError} Rejects: with an error response. */ - public async getAccountDataFromServer(eventType: string): Promise> { + public async getAccountDataFromServer(eventType: string): Promise { if (this.isInitialSyncComplete()) { const event = this.store.getAccountData(eventType); if (!event) { @@ -3201,7 +3201,7 @@ export class MatrixClient extends EventEmitter { ); } - const queryString = {}; + const queryString: Record = {}; if (opts.viaServers) { queryString["server_name"] = opts.viaServers; } @@ -3411,7 +3411,7 @@ export class MatrixClient extends EventEmitter { content: IContent, txnId?: string, callback?: Callback, - ); + ): Promise; public sendEvent( roomId: string, threadId: string | null, @@ -3419,7 +3419,7 @@ export class MatrixClient extends EventEmitter { content: IContent, txnId?: string, callback?: Callback, - ) + ): Promise; public sendEvent( roomId: string, threadId: string | null, @@ -3699,14 +3699,14 @@ export class MatrixClient extends EventEmitter { eventId: string, txnId?: string | undefined, cbOrOpts?: Callback | IRedactOpts, - ); + ): Promise; public redactEvent( roomId: string, threadId: string | null, eventId: string, txnId?: string | undefined, cbOrOpts?: Callback | IRedactOpts, - ); + ): Promise; public redactEvent( roomId: string, threadId: string | null, @@ -3744,14 +3744,14 @@ export class MatrixClient extends EventEmitter { content: IContent, txnId?: string, callback?: Callback, - ) + ): Promise; public sendMessage( roomId: string, threadId: string | null, content: IContent, txnId?: string, callback?: Callback, - ) + ): Promise; public sendMessage( roomId: string, threadId: string | null | IContent, @@ -3793,14 +3793,14 @@ export class MatrixClient extends EventEmitter { body: string, txnId?: string, callback?: Callback, - ) + ): Promise; public sendTextMessage( roomId: string, threadId: string | null, body: string, txnId?: string, callback?: Callback, - ) + ): Promise; public sendTextMessage( roomId: string, threadId: string | null, @@ -3832,14 +3832,14 @@ export class MatrixClient extends EventEmitter { body: string, txnId?: string, callback?: Callback, - ) + ): Promise; public sendNotice( roomId: string, threadId: string | null, body: string, txnId?: string, callback?: Callback, - ); + ): Promise; public sendNotice( roomId: string, threadId: string | null, @@ -3871,14 +3871,14 @@ export class MatrixClient extends EventEmitter { body: string, txnId?: string, callback?: Callback, - ) + ): Promise; public sendEmoteMessage( roomId: string, threadId: string | null, body: string, txnId?: string, callback?: Callback, - ); + ): Promise; public sendEmoteMessage( roomId: string, threadId: string | null, @@ -3912,7 +3912,7 @@ export class MatrixClient extends EventEmitter { info?: IImageInfo, text?: string, callback?: Callback, - ); + ): Promise; public sendImageMessage( roomId: string, threadId: string | null, @@ -3920,7 +3920,7 @@ export class MatrixClient extends EventEmitter { info?: IImageInfo, text?: string, callback?: Callback, - ); + ): Promise; public sendImageMessage( roomId: string, threadId: string | null, @@ -3965,7 +3965,7 @@ export class MatrixClient extends EventEmitter { info?: IImageInfo, text?: string, callback?: Callback, - ); + ): Promise; public sendStickerMessage( roomId: string, threadId: string | null, @@ -3973,7 +3973,7 @@ export class MatrixClient extends EventEmitter { info?: IImageInfo, text?: string, callback?: Callback, - ); + ): Promise; public sendStickerMessage( roomId: string, threadId: string | null, @@ -4015,14 +4015,14 @@ export class MatrixClient extends EventEmitter { body: string, htmlBody: string, callback?: Callback, - ); + ): Promise; public sendHtmlMessage( roomId: string, threadId: string | null, body: string, htmlBody: string, callback?: Callback, - ) + ): Promise; public sendHtmlMessage( roomId: string, threadId: string | null, @@ -4053,14 +4053,14 @@ export class MatrixClient extends EventEmitter { body: string, htmlBody: string, callback?: Callback, - ); + ): Promise; public sendHtmlNotice( roomId: string, threadId: string | null, body: string, htmlBody: string, callback?: Callback, - ) + ): Promise; public sendHtmlNotice( roomId: string, threadId: string | null, @@ -4092,14 +4092,14 @@ export class MatrixClient extends EventEmitter { body: string, htmlBody: string, callback?: Callback, - ); + ): Promise; public sendHtmlEmote( roomId: string, threadId: string | null, body: string, htmlBody: string, callback?: Callback, - ) + ): Promise; public sendHtmlEmote( roomId: string, threadId: string | null, @@ -4420,7 +4420,7 @@ export class MatrixClient extends EventEmitter { errcode: "ORG.MATRIX.JSSDK_MISSING_PARAM", })); } - const params = { + const params: Record = { id_server: identityServerUrl, medium: medium, address: address, @@ -4478,10 +4478,10 @@ export class MatrixClient extends EventEmitter { } } - const populationResults = {}; // {roomId: Error} + const populationResults: Record = {}; // {roomId: Error} const promises = []; - const doLeave = (roomId) => { + const doLeave = (roomId: string) => { return this.leave(roomId).then(() => { populationResults[roomId] = null; }).catch((err) => { @@ -5065,14 +5065,14 @@ export class MatrixClient extends EventEmitter { return pendingRequest; } - let path; - let params; + let path: string; + let params: Record; let promise; if (isNotifTimeline) { path = "/notifications"; params = { - limit: ('limit' in opts) ? opts.limit : 30, + limit: (opts.limit ?? 30).toString(), only: 'highlight', }; @@ -5509,7 +5509,7 @@ export class MatrixClient extends EventEmitter { * @return {module:http-api.MatrixError} Rejects: with an error response. */ public setRoomMutePushRule(scope: string, roomId: string, mute: boolean): Promise | void { - let deferred; + let promise: Promise; let hasDontNotifyRule; // Get the existing room-kind push rule if any @@ -5523,17 +5523,17 @@ export class MatrixClient extends EventEmitter { if (!mute) { // Remove the rule only if it is a muting rule if (hasDontNotifyRule) { - deferred = this.deletePushRule(scope, PushRuleKind.RoomSpecific, roomPushRule.rule_id); + promise = this.deletePushRule(scope, PushRuleKind.RoomSpecific, roomPushRule.rule_id); } } else { if (!roomPushRule) { - deferred = this.addPushRule(scope, PushRuleKind.RoomSpecific, roomId, { + promise = this.addPushRule(scope, PushRuleKind.RoomSpecific, roomId, { actions: ["dont_notify"], }); } else if (!hasDontNotifyRule) { // Remove the existing one before setting the mute push rule // This is a workaround to SYN-590 (Push rule update fails) - deferred = utils.defer(); + const deferred = utils.defer(); this.deletePushRule(scope, PushRuleKind.RoomSpecific, roomPushRule.rule_id) .then(() => { this.addPushRule(scope, PushRuleKind.RoomSpecific, roomId, { @@ -5547,21 +5547,21 @@ export class MatrixClient extends EventEmitter { deferred.reject(err); }); - deferred = deferred.promise; + promise = deferred.promise; } } - if (deferred) { + if (promise) { return new Promise((resolve, reject) => { // Update this.pushRules when the operation completes - deferred.then(() => { + promise.then(() => { this.getPushRules().then((result) => { this.pushRules = result; resolve(); }).catch((err) => { reject(err); }); - }).catch((err) => { + }).catch((err: Error) => { // Update it even if the previous operation fails. This can help the // app to recover when push settings has been modifed from another client this.getPushRules().then((result) => { @@ -6281,7 +6281,7 @@ export class MatrixClient extends EventEmitter { fetchedEventType, opts); const mapper = this.getEventMapper(); - let originalEvent; + let originalEvent: MatrixEvent; if (result.original_event) { originalEvent = mapper(result.original_event); } @@ -6564,7 +6564,7 @@ export class MatrixClient extends EventEmitter { }; // merge data into loginData - utils.extend(loginData, data); + Object.assign(loginData, data); return this.http.authedRequest( (error, response) => { @@ -7544,7 +7544,7 @@ export class MatrixClient extends EventEmitter { * @return {module:http-api.MatrixError} Rejects: with an error response. */ public getPushRules(callback?: Callback): Promise { - return this.http.authedRequest(callback, "GET", "/pushrules/").then(rules => { + return this.http.authedRequest(callback, "GET", "/pushrules/").then((rules: IPushRules) => { return PushProcessor.rewriteDefaultRules(rules); }); } @@ -8018,7 +8018,7 @@ export class MatrixClient extends EventEmitter { addressPairs: [string, string][], identityAccessToken: string, ): Promise<{ address: string, mxid: string }[]> { - const params = { + const params: Record = { // addresses: ["email@example.org", "10005550000"], // algorithm: "sha256", // pepper: "abc123" @@ -8032,7 +8032,7 @@ export class MatrixClient extends EventEmitter { params['pepper'] = hashes['lookup_pepper']; - const localMapping = { + const localMapping: Record = { // hashed identifier => plain text address // For use in this function's return format }; diff --git a/src/content-repo.ts b/src/content-repo.ts index 287259651..febd0d1c9 100644 --- a/src/content-repo.ts +++ b/src/content-repo.ts @@ -53,13 +53,13 @@ export function getHttpUriForMxc( } let serverAndMediaId = mxc.slice(6); // strips mxc:// let prefix = "/_matrix/media/r0/download/"; - const params = {}; + const params: Record = {}; if (width) { - params["width"] = Math.round(width); + params["width"] = Math.round(width).toString(); } if (height) { - params["height"] = Math.round(height); + params["height"] = Math.round(height).toString(); } if (resizeMethod) { params["method"] = resizeMethod; diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index d27af3c54..c8a87cc56 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -265,8 +265,8 @@ export class DeviceList extends EventEmitter { * module:crypto/deviceinfo|DeviceInfo}. */ public downloadKeys(userIds: string[], forceDownload: boolean): Promise { - const usersToDownload = []; - const promises = []; + const usersToDownload: string[] = []; + const promises: Promise[] = []; userIds.forEach((u) => { const trackingStatus = this.deviceTrackingStatus[u]; @@ -633,7 +633,7 @@ export class DeviceList extends EventEmitter { } }); - const finished = (success) => { + const finished = (success: boolean): void => { this.emit("crypto.willUpdateDevices", users, !this.hasFetched); users.forEach((u) => { this.dirty = true; diff --git a/src/crypto/SecretStorage.ts b/src/crypto/SecretStorage.ts index fb5665fa6..877d7507c 100644 --- a/src/crypto/SecretStorage.ts +++ b/src/crypto/SecretStorage.ts @@ -37,7 +37,7 @@ export interface ISecretRequest { export interface IAccountDataClient extends EventEmitter { // Subset of MatrixClient (which also uses any for the event content) - getAccountDataFromServer: (eventType: string) => Promise>; + getAccountDataFromServer: (eventType: string) => Promise; getAccountData: (eventType: string) => MatrixEvent; setAccountData: (eventType: string, content: any) => Promise<{}>; } @@ -76,7 +76,7 @@ export class SecretStorage { ) {} public async getDefaultKeyId(): Promise { - const defaultKey = await this.accountDataAdapter.getAccountDataFromServer( + const defaultKey = await this.accountDataAdapter.getAccountDataFromServer<{ key: string }>( 'm.secret_storage.default_key', ); if (!defaultKey) return null; @@ -230,7 +230,7 @@ export class SecretStorage { * or null/undefined to use the default key. */ public async store(name: string, secret: string, keys?: string[]): Promise { - const encrypted = {}; + const encrypted: Record = {}; if (!keys) { const defaultKeyId = await this.getDefaultKeyId(); @@ -246,9 +246,9 @@ export class SecretStorage { for (const keyId of keys) { // get key information from key storage - const keyInfo = await this.accountDataAdapter.getAccountDataFromServer( + const keyInfo = await this.accountDataAdapter.getAccountDataFromServer( "m.secret_storage.key." + keyId, - ) as ISecretStorageKeyInfo; + ); if (!keyInfo) { throw new Error("Unknown key: " + keyId); } @@ -277,7 +277,7 @@ export class SecretStorage { * @return {string} the contents of the secret */ public async get(name: string): Promise { - const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name); + const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name); // TODO types if (!secretInfo) { return; } @@ -286,11 +286,13 @@ export class SecretStorage { } // get possible keys to decrypt - const keys = {}; + const keys: Record = {}; for (const keyId of Object.keys(secretInfo.encrypted)) { // get key information from key storage - const keyInfo = await this.accountDataAdapter.getAccountDataFromServer( - "m.secret_storage.key." + keyId, + const keyInfo = ( + await this.accountDataAdapter.getAccountDataFromServer( + "m.secret_storage.key." + keyId, + ) ); const encInfo = secretInfo.encrypted[keyId]; // only use keys we understand the encryption algorithm of @@ -306,7 +308,7 @@ export class SecretStorage { `the keys it is encrypted with are for a supported algorithm`); } - let keyId; + let keyId: string; let decryption; try { // fetch private key from app @@ -337,7 +339,7 @@ export class SecretStorage { */ public async isStored(name: string, checkKey: boolean): Promise> { // check if secret exists - const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name); + const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name); // TODO types if (!secretInfo) return null; if (!secretInfo.encrypted) { return null; @@ -350,7 +352,7 @@ export class SecretStorage { // filter secret encryption keys with supported algorithm for (const keyId of Object.keys(secretInfo.encrypted)) { // get key information from key storage - const keyInfo = await this.accountDataAdapter.getAccountDataFromServer( + const keyInfo = await this.accountDataAdapter.getAccountDataFromServer( // TODO types "m.secret_storage.key." + keyId, ); if (!keyInfo) continue; @@ -375,8 +377,8 @@ export class SecretStorage { public request(name: string, devices: string[]): ISecretRequest { const requestId = this.baseApis.makeTxnId(); - let resolve: (string) => void; - let reject: (Error) => void; + let resolve: (s: string) => void; + let reject: (e: Error) => void; const promise = new Promise((res, rej) => { resolve = res; reject = rej; diff --git a/src/crypto/algorithms/base.ts b/src/crypto/algorithms/base.ts index 89fa7034d..add9111ef 100644 --- a/src/crypto/algorithms/base.ts +++ b/src/crypto/algorithms/base.ts @@ -46,7 +46,7 @@ type DecryptionClassParams = Omit; */ export const DECRYPTION_CLASSES: Record DecryptionAlgorithm> = {}; -interface IParams { +export interface IParams { userId: string; deviceId: string; crypto: Crypto; diff --git a/src/crypto/algorithms/megolm.ts b/src/crypto/algorithms/megolm.ts index 167c81130..d5a591997 100644 --- a/src/crypto/algorithms/megolm.ts +++ b/src/crypto/algorithms/megolm.ts @@ -26,6 +26,7 @@ import { DecryptionAlgorithm, DecryptionError, EncryptionAlgorithm, + IParams, registerAlgorithm, UnknownDeviceError, } from "./base"; @@ -99,6 +100,12 @@ interface IPayload extends Partial { algorithm?: string; sender_key?: string; } + +interface IEncryptedContent { + algorithm: string; + sender_key: string; + ciphertext: Record; +} /* eslint-enable camelcase */ interface SharedWithData { @@ -238,7 +245,7 @@ class MegolmEncryption extends EncryptionAlgorithm { startTime: number; }; - constructor(params) { + constructor(params: IParams) { super(params); this.sessionRotationPeriodMsgs = params.config?.rotation_period_msgs ?? 100; @@ -263,7 +270,7 @@ class MegolmEncryption extends EncryptionAlgorithm { blocked: IBlockedMap, singleOlmCreationPhase = false, ): Promise { - let session; + let session: OutboundSessionInfo; // takes the previous OutboundSessionInfo, and considers whether to create // a new one. Also shares the key with any (new) devices in the room. @@ -302,7 +309,7 @@ class MegolmEncryption extends EncryptionAlgorithm { } // now check if we need to share with any devices - const shareMap = {}; + const shareMap: Record = {}; for (const [userId, userDevices] of Object.entries(devicesInRoom)) { for (const [deviceId, deviceInfo] of Object.entries(userDevices)) { @@ -350,7 +357,7 @@ class MegolmEncryption extends EncryptionAlgorithm { `Sharing keys (start phase 1) with new Olm sessions in ${this.roomId}`, devicesWithoutSession, ); - const errorDevices = []; + const errorDevices: IOlmDevice[] = []; // meanwhile, establish olm sessions for devices that we don't // already have a session for, and share keys with them. If @@ -358,7 +365,7 @@ class MegolmEncryption extends EncryptionAlgorithm { // shorter timeout when fetching one-time keys for the first // phase. const start = Date.now(); - const failedServers = []; + const failedServers: string[] = []; await this.shareKeyWithDevices( session, key, payload, devicesWithoutSession, errorDevices, singleOlmCreationPhase ? 10000 : 2000, failedServers, @@ -374,7 +381,7 @@ class MegolmEncryption extends EncryptionAlgorithm { // do this in the background and don't block anything else while we // do this. We only need to retry users from servers that didn't // respond the first time. - const retryDevices = {}; + const retryDevices: Record = {}; const failedServerMap = new Set; for (const server of failedServers) { failedServerMap.add(server); @@ -584,12 +591,12 @@ class MegolmEncryption extends EncryptionAlgorithm { userDeviceMap: IOlmDevice[], payload: IPayload, ): Promise { - const contentMap = {}; + const contentMap: Record> = {}; const deviceInfoByDeviceId = new Map(); - const promises = []; + const promises: Promise[] = []; for (let i = 0; i < userDeviceMap.length; i++) { - const encryptedContent = { + const encryptedContent: IEncryptedContent = { algorithm: olmlib.OLM_ALGORITHM, sender_key: this.olmDevice.deviceCurve25519Key, ciphertext: {}, @@ -679,7 +686,7 @@ class MegolmEncryption extends EncryptionAlgorithm { userDeviceMap: IOlmDevice[], payload: IPayload, ): Promise { - const contentMap = {}; + const contentMap: Record> = {}; for (const val of userDeviceMap) { const userId = val.userId; @@ -1105,7 +1112,7 @@ class MegolmEncryption extends EncryptionAlgorithm { * devices we should shared the session with. */ private checkForUnknownDevices(devicesInRoom: DeviceInfoMap): void { - const unknownDevices = {}; + const unknownDevices: Record> = {}; Object.keys(devicesInRoom).forEach((userId)=>{ Object.keys(devicesInRoom[userId]).forEach((deviceId)=>{ @@ -1304,8 +1311,7 @@ class MegolmDecryption extends DecryptionAlgorithm { content.sender_key, event.getTs() - 120000, ); if (problem) { - let problemDescription = PROBLEM_DESCRIPTIONS[problem.type] - || PROBLEM_DESCRIPTIONS.unknown; + let problemDescription = PROBLEM_DESCRIPTIONS[problem.type as "no_olm"] || PROBLEM_DESCRIPTIONS.unknown; if (problem.fixed) { problemDescription += " Trying to create a new secure channel and re-requesting the keys."; @@ -1399,14 +1405,14 @@ class MegolmDecryption extends DecryptionAlgorithm { const senderKey = content.sender_key; const sessionId = content.session_id; const senderPendingEvents = this.pendingEvents[senderKey]; - const pendingEvents = senderPendingEvents && senderPendingEvents.get(sessionId); + const pendingEvents = senderPendingEvents?.get(sessionId); if (!pendingEvents) { return; } pendingEvents.delete(event); if (pendingEvents.size === 0) { - senderPendingEvents.delete(senderKey); + senderPendingEvents.delete(sessionId); } if (senderPendingEvents.size === 0) { delete this.pendingEvents[senderKey]; @@ -1760,7 +1766,7 @@ class MegolmDecryption extends DecryptionAlgorithm { })); // If decrypted successfully, they'll have been removed from pendingEvents - return !((this.pendingEvents[senderKey] || {})[sessionId]); + return !this.pendingEvents[senderKey]?.has(sessionId); } public async retryDecryptionFromSender(senderKey: string): Promise { @@ -1794,12 +1800,12 @@ class MegolmDecryption extends DecryptionAlgorithm { for (const [senderKey, sessionId] of sharedHistorySessions) { const payload = await this.buildKeyForwardingMessage(this.roomId, senderKey, sessionId); - const promises = []; - const contentMap = {}; + const promises: Promise[] = []; + const contentMap: Record> = {}; for (const [userId, devices] of Object.entries(devicesByUser)) { contentMap[userId] = {}; for (const deviceInfo of devices) { - const encryptedContent = { + const encryptedContent: IEncryptedContent = { algorithm: olmlib.OLM_ALGORITHM, sender_key: this.olmDevice.deviceCurve25519Key, ciphertext: {}, diff --git a/src/crypto/algorithms/olm.ts b/src/crypto/algorithms/olm.ts index 932871783..369178737 100644 --- a/src/crypto/algorithms/olm.ts +++ b/src/crypto/algorithms/olm.ts @@ -282,7 +282,7 @@ class OlmDecryption extends DecryptionAlgorithm { const sessionIds = await this.olmDevice.getSessionIdsForDevice(theirDeviceIdentityKey); // try each session in turn. - const decryptionErrors = {}; + const decryptionErrors: Record = {}; for (let i = 0; i < sessionIds.length; i++) { const sessionId = sessionIds[i]; try { diff --git a/src/crypto/index.ts b/src/crypto/index.ts index be30eb8bb..828067461 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -58,7 +58,7 @@ import { BackupManager } from "./backup"; import { IStore } from "../store"; import { Room } from "../models/room"; import { RoomMember } from "../models/room-member"; -import { MatrixEvent, EventStatus } from "../models/event"; +import { MatrixEvent, EventStatus, IClearEvent } from "../models/event"; import { MatrixClient, IKeysUploadResponse, SessionStore, ISignedKey, ICrossSigningKey } from "../client"; import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base"; import type { IRoomEncryption, RoomList } from "./RoomList"; @@ -172,10 +172,10 @@ interface ISignableObject { } export interface IEventDecryptionResult { - clearEvent: object; + clearEvent: IClearEvent; + forwardingCurve25519KeyChain?: string[]; senderCurve25519Key?: string; claimedEd25519Key?: string; - forwardingCurve25519KeyChain?: string[]; untrusted?: boolean; } diff --git a/src/crypto/keybackup.ts b/src/crypto/keybackup.ts index 4d8981ff8..1b7ae5240 100644 --- a/src/crypto/keybackup.ts +++ b/src/crypto/keybackup.ts @@ -67,5 +67,5 @@ export interface IKeyBackupRestoreResult { export interface IKeyBackupRestoreOpts { cacheCompleteCallback?: () => void; - progressCallback?: ({ stage: string }) => void; + progressCallback?: (progress: { stage: string }) => void; } diff --git a/src/crypto/olmlib.ts b/src/crypto/olmlib.ts index 72268c3a2..f0424f672 100644 --- a/src/crypto/olmlib.ts +++ b/src/crypto/olmlib.ts @@ -27,7 +27,6 @@ import { Logger } from "loglevel"; import { OlmDevice } from "./OlmDevice"; import { DeviceInfo } from "./deviceinfo"; import { logger } from '../logger'; -import * as utils from "../utils"; import { IOneTimeKey } from "./dehydration"; import { MatrixClient } from "../client"; @@ -126,7 +125,7 @@ export async function encryptMessageForDevice( // involved in the session. If we're looking to reduce data transfer in the // future, we could elide them for subsequent messages. - utils.extend(payload, payloadFields); + Object.assign(payload, payloadFields); resultsObject[deviceKey] = await olmDevice.encryptMessage( deviceKey, sessionId, JSON.stringify(payload), diff --git a/src/crypto/verification/request/InRoomChannel.ts b/src/crypto/verification/request/InRoomChannel.ts index 98dd58e5b..f07c39ffb 100644 --- a/src/crypto/verification/request/InRoomChannel.ts +++ b/src/crypto/verification/request/InRoomChannel.ts @@ -36,7 +36,7 @@ const M_RELATES_TO = "m.relates_to"; * Uses the event id of the initial m.key.verification.request event as a transaction id. */ export class InRoomChannel implements IVerificationChannel { - private requestEventId = null; + private requestEventId: string = null; /** * @param {MatrixClient} client the matrix client, to send messages with and get current user & device from. diff --git a/src/http-api.js b/src/http-api.js index 92c6e420f..a3d3c155b 100644 --- a/src/http-api.js +++ b/src/http-api.js @@ -657,7 +657,7 @@ MatrixHttpApi.prototype = { }; } - const headers = utils.extend({}, opts.headers || {}); + const headers = Object.assign({}, opts.headers || {}); const json = opts.json === undefined ? true : opts.json; let bodyParser = opts.bodyParser; diff --git a/src/interactive-auth.ts b/src/interactive-auth.ts index 74d5d30ca..eb00c75db 100644 --- a/src/interactive-auth.ts +++ b/src/interactive-auth.ts @@ -18,7 +18,6 @@ limitations under the License. /** @module interactive-auth */ -import * as utils from "./utils"; import { logger } from './logger'; import { MatrixClient } from "./client"; import { defer, IDeferred } from "./utils"; @@ -68,7 +67,7 @@ export enum AuthType { export interface IAuthDict { // [key: string]: any; type?: string; - // session?: string; // TODO + session?: string; // TODO: Remove `user` once servers support proper UIA // See https://github.com/vector-im/element-web/issues/10312 user?: string; @@ -360,12 +359,12 @@ export class InteractiveAuth { } // use the sessionid from the last request, if one is present. - let auth; + let auth: IAuthDict; if (this.data.session) { auth = { session: this.data.session, }; - utils.extend(auth, authData); + Object.assign(auth, authData); } else { auth = authData; } diff --git a/src/logger.ts b/src/logger.ts index 04763cd33..6084314a3 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -76,7 +76,7 @@ function extendLogger(logger: PrefixedLogger) { extendLogger(logger); -function getPrefixedLogger(prefix): PrefixedLogger { +function getPrefixedLogger(prefix: string): PrefixedLogger { const prefixLogger: PrefixedLogger = log.getLogger(`${DEFAULT_NAMESPACE}-${prefix}`); if (prefixLogger.prefix !== prefix) { // Only do this setup work the first time through, as loggers are saved by name. diff --git a/src/matrix.ts b/src/matrix.ts index e279997e9..f43d35d72 100644 --- a/src/matrix.ts +++ b/src/matrix.ts @@ -121,7 +121,7 @@ export interface ICryptoCallbacks { ) => Promise; getDehydrationKey?: ( keyInfo: ISecretStorageKeyInfo, - checkFunc: (Uint8Array) => void, + checkFunc: (key: Uint8Array) => void, ) => Promise; getBackupKey?: () => Promise; } diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index 1897d6741..b5c6e4800 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -33,7 +33,7 @@ import { Thread } from "./thread"; // var DEBUG = false; const DEBUG = true; -let debuglog; +let debuglog: (...args: any[]) => void; if (DEBUG) { // using bind means that we get to keep useful line numbers in the console debuglog = logger.log.bind(logger); diff --git a/src/models/event.ts b/src/models/event.ts index 7b24ffe1a..29f2df904 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -29,7 +29,7 @@ import { MsgType, RelationType, } from "../@types/event"; -import { Crypto } from "../crypto"; +import { Crypto, IEventDecryptionResult } from "../crypto"; import { deepSortedObjectEntries } from "../utils"; import { RoomMember } from "./room-member"; import { Thread, ThreadEvent } from "./thread"; @@ -85,7 +85,7 @@ export interface IUnsigned { age?: number; prev_sender?: string; prev_content?: IContent; - redacted_because?: IEvent; + redacted_because?: IClearEvent; transaction_id?: string; invite_room_state?: StrippedState[]; } @@ -124,25 +124,13 @@ export interface IEventRelation { key?: string; } -interface IDecryptionResult { - clearEvent: { - room_id?: string; - type: string; - content: IContent; - unsigned?: IUnsigned; - }; - forwardingCurve25519KeyChain?: string[]; - senderCurve25519Key?: string; - claimedEd25519Key?: string; - untrusted?: boolean; -} -/* eslint-enable camelcase */ - export interface IClearEvent { + room_id?: string; type: string; content: Omit; unsigned?: IUnsigned; } +/* eslint-enable camelcase */ interface IKeyRequestRecipient { userId: string; @@ -215,14 +203,14 @@ export class MatrixEvent extends EventEmitter { public sender: RoomMember = null; public target: RoomMember = null; public status: EventStatus = null; - public error = null; + public error: Error = null; public forwardLooking = true; /* If the event is a `m.key.verification.request` (or to_device `m.key.verification.start`) event, * `Crypto` will set this the `VerificationRequest` for the event * so it can be easily accessed from the timeline. */ - public verificationRequest = null; + public verificationRequest: VerificationRequest = null; private readonly reEmitter: ReEmitter; @@ -690,8 +678,8 @@ export class MatrixEvent extends EventEmitter { while (true) { this.retryDecryption = false; - let res; - let err; + let res: IEventDecryptionResult; + let err: Error; try { if (!crypto) { res = this.badEncryptedMessage("Encryption not enabled"); @@ -779,7 +767,7 @@ export class MatrixEvent extends EventEmitter { } } - private badEncryptedMessage(reason: string): IDecryptionResult { + private badEncryptedMessage(reason: string): IEventDecryptionResult { return { clearEvent: { type: "m.room.message", @@ -803,7 +791,7 @@ export class MatrixEvent extends EventEmitter { * @param {module:crypto~EventDecryptionResult} decryptionResult * the decryption result, including the plaintext and some key info */ - private setClearData(decryptionResult: IDecryptionResult): void { + private setClearData(decryptionResult: IEventDecryptionResult): void { this.clearEvent = decryptionResult.clearEvent; this.senderCurve25519Key = decryptionResult.senderCurve25519Key || null; diff --git a/src/models/room.ts b/src/models/room.ts index e66679ee8..d22082b53 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -48,19 +48,19 @@ const SAFE_ROOM_VERSIONS = ['1', '2', '3', '4', '5', '6']; function synthesizeReceipt(userId: string, event: MatrixEvent, receiptType: string): MatrixEvent { // console.log("synthesizing receipt for "+event.getId()); - // This is really ugly because JS has no way to express an object literal - // where the name of a key comes from an expression - const fakeReceipt = { - content: {}, + return new MatrixEvent({ + content: { + [event.getId()]: { + [receiptType]: { + [userId]: { + ts: event.getTs(), + }, + }, + }, + }, type: "m.receipt", room_id: event.getRoomId(), - }; - fakeReceipt.content[event.getId()] = {}; - fakeReceipt.content[event.getId()][receiptType] = {}; - fakeReceipt.content[event.getId()][receiptType][userId] = { - ts: event.getTs(), - }; - return new MatrixEvent(fakeReceipt); + }); } interface IOpts { @@ -240,7 +240,7 @@ export class Room extends EventEmitter { const serializedPendingEventList = client.sessionStore.store.getItem(pendingEventsKey(this.roomId)); if (serializedPendingEventList) { JSON.parse(serializedPendingEventList) - .forEach(async serializedEvent => { + .forEach(async (serializedEvent: Partial) => { const event = new MatrixEvent(serializedEvent); if (event.getType() === EventType.RoomMessageEncrypted) { await event.attemptDecryption(this.client.crypto); @@ -997,14 +997,14 @@ export class Room extends EventEmitter { * @return {array} The room's alias as an array of strings */ public getAliases(): string[] { - const aliasStrings = []; + const aliasStrings: string[] = []; const aliasEvents = this.currentState.getStateEvents(EventType.RoomAliases); if (aliasEvents) { for (let i = 0; i < aliasEvents.length; ++i) { const aliasEvent = aliasEvents[i]; if (Array.isArray(aliasEvent.getContent().aliases)) { - const filteredAliases = aliasEvent.getContent().aliases.filter(a => { + const filteredAliases = aliasEvent.getContent<{ aliases: string[] }>().aliases.filter(a => { if (typeof(a) !== "string") return false; if (a[0] !== '#') return false; if (!a.endsWith(`:${aliasEvent.getStateKey()}`)) return false; @@ -1992,7 +1992,7 @@ export class Room extends EventEmitter { * @return {Object} Map of receipts by event ID */ private buildReceiptCache(receipts: Receipts): ReceiptCache { - const receiptCacheByEventId = {}; + const receiptCacheByEventId: ReceiptCache = {}; Object.keys(receipts).forEach(function(receiptType) { Object.keys(receipts[receiptType]).forEach(function(userId) { const receipt = receipts[receiptType][userId]; @@ -2185,7 +2185,7 @@ export class Room extends EventEmitter { } // get members that are NOT ourselves and are actually in the room. - let otherNames = null; + let otherNames: string[] = null; if (this.summaryHeroes) { // if we have a summary, the member state events // should be in the room state @@ -2266,31 +2266,29 @@ function pendingEventsKey(roomId: string): string { /* a map from current event status to a list of allowed next statuses */ -const ALLOWED_TRANSITIONS = {}; - -ALLOWED_TRANSITIONS[EventStatus.ENCRYPTING] = [ - EventStatus.SENDING, - EventStatus.NOT_SENT, -]; - -ALLOWED_TRANSITIONS[EventStatus.SENDING] = [ - EventStatus.ENCRYPTING, - EventStatus.QUEUED, - EventStatus.NOT_SENT, - EventStatus.SENT, -]; - -ALLOWED_TRANSITIONS[EventStatus.QUEUED] = - [EventStatus.SENDING, EventStatus.CANCELLED]; - -ALLOWED_TRANSITIONS[EventStatus.SENT] = - []; - -ALLOWED_TRANSITIONS[EventStatus.NOT_SENT] = - [EventStatus.SENDING, EventStatus.QUEUED, EventStatus.CANCELLED]; - -ALLOWED_TRANSITIONS[EventStatus.CANCELLED] = - []; +const ALLOWED_TRANSITIONS: Record = { + [EventStatus.ENCRYPTING]: [ + EventStatus.SENDING, + EventStatus.NOT_SENT, + ], + [EventStatus.SENDING]: [ + EventStatus.ENCRYPTING, + EventStatus.QUEUED, + EventStatus.NOT_SENT, + EventStatus.SENT, + ], + [EventStatus.QUEUED]: [ + EventStatus.SENDING, + EventStatus.CANCELLED, + ], + [EventStatus.SENT]: [], + [EventStatus.NOT_SENT]: [ + EventStatus.SENDING, + EventStatus.QUEUED, + EventStatus.CANCELLED, + ], + [EventStatus.CANCELLED]: [], +}; // TODO i18n function memberNamesToRoomName(names: string[], count = (names.length + 1)) { diff --git a/src/scheduler.ts b/src/scheduler.ts index b83a57eba..d0436bd83 100644 --- a/src/scheduler.ts +++ b/src/scheduler.ts @@ -284,7 +284,7 @@ export class MatrixScheduler { } } -function debuglog(...args) { +function debuglog(...args: any[]) { if (DEBUG) { logger.log(...args); } diff --git a/src/store/index.ts b/src/store/index.ts index ad25a1c7e..f4bf214a7 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -51,14 +51,14 @@ export interface IStore { * Set the sync token. * @param {string} token */ - setSyncToken(token: string); + setSyncToken(token: string): void; /** * No-op. * @param {Group} group * @deprecated groups/communities never made it to the spec and support for them is being discontinued. */ - storeGroup(group: Group); + storeGroup(group: Group): void; /** * No-op. @@ -79,7 +79,7 @@ export interface IStore { * No-op. * @param {Room} room */ - storeRoom(room: Room); + storeRoom(room: Room): void; /** * No-op. @@ -98,7 +98,7 @@ export interface IStore { * Permanently delete a room. * @param {string} roomId */ - removeRoom(roomId: string); + removeRoom(roomId: string): void; /** * No-op. @@ -110,7 +110,7 @@ export interface IStore { * No-op. * @param {User} user */ - storeUser(user: User); + storeUser(user: User): void; /** * No-op. @@ -140,13 +140,13 @@ export interface IStore { * @param {string} token The token associated with these events. * @param {boolean} toStart True if these are paginated results. */ - storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean); + storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean): void; /** * Store a filter. * @param {Filter} filter */ - storeFilter(filter: Filter); + storeFilter(filter: Filter): void; /** * Retrieve a filter. @@ -168,13 +168,13 @@ export interface IStore { * @param {string} filterName * @param {string} filterId */ - setFilterIdByName(filterName: string, filterId: string); + setFilterIdByName(filterName: string, filterId: string): void; /** * Store user-scoped account data events * @param {Array} events The events to store. */ - storeAccountDataEvents(events: MatrixEvent[]); + storeAccountDataEvents(events: MatrixEvent[]): void; /** * Get account data event by event type diff --git a/src/store/indexeddb-local-backend.ts b/src/store/indexeddb-local-backend.ts index c5c25fc19..2e5ef0cfd 100644 --- a/src/store/indexeddb-local-backend.ts +++ b/src/store/indexeddb-local-backend.ts @@ -66,7 +66,7 @@ function selectQuery( ): Promise { const query = store.openCursor(keyRange); return new Promise((resolve, reject) => { - const results = []; + const results: T[] = []; query.onerror = () => { reject(new Error("Query failed: " + query.error)); }; @@ -238,7 +238,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { const range = IDBKeyRange.only(roomId); const request = roomIndex.openCursor(range); - const membershipEvents = []; + const membershipEvents: IEvent[] = []; // did we encounter the oob_written marker object // amongst the results? That means OOB member // loading already happened for this room diff --git a/src/utils.ts b/src/utils.ts index d24f1601d..b18aadf49 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -90,23 +90,20 @@ export function removeElement( array: T[], fn: (t: T, i?: number, a?: T[]) => boolean, reverse?: boolean, -) { - let i; - let removed; +): boolean { + let i: number; if (reverse) { for (i = array.length - 1; i >= 0; i--) { if (fn(array[i], i, array)) { - removed = array[i]; array.splice(i, 1); - return removed; + return true; } } } else { for (i = 0; i < array.length; i++) { if (fn(array[i], i, array)) { - removed = array[i]; array.splice(i, 1); - return removed; + return true; } } } @@ -276,31 +273,6 @@ export function deepSortedObjectEntries(obj: any): [string, any][] { return pairs; } -/** - * Copy properties from one object to another. - * - * All enumerable properties, included inherited ones, are copied. - * - * This is approximately equivalent to ES6's Object.assign, except - * that the latter doesn't copy inherited properties. - * - * @param {Object} target The object that will receive new properties - * @param {...Object} source Objects from which to copy properties - * - * @return {Object} target - */ -export function extend(...restParams) { - const target = restParams[0] || {}; - for (let i = 1; i < restParams.length; i++) { - const source = restParams[i]; - if (!source) continue; - for (const propName in source) { // eslint-disable-line guard-for-in - target[propName] = source[propName]; - } - } - return target; -} - /** * Inherit the prototype methods from one constructor into another. This is a * port of the Node.js implementation with an Object.create polyfill.