diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b345f3fc8f..75f25b7f4b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +Changes in [15.2.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.2.1) (2021-12-13) +================================================================================================== + + * Security release with updated version of Olm to fix https://matrix.org/blog/2021/12/03/pre-disclosure-upcoming-security-release-of-libolm-and-matrix-js-sdk + Changes in [15.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.2.0) (2021-12-06) ================================================================================================== diff --git a/package.json b/package.json index 280ef9da77a..4f6059a3c54 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "15.2.0", + "version": "15.2.1", "description": "Matrix Client-Server SDK for Javascript", "scripts": { "prepublishOnly": "yarn build", @@ -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 4 src spec", + "lint:js": "eslint --max-warnings 0 src spec", "lint:js-fix": "eslint --fix src spec", "lint:types": "tsc --noEmit", "test": "jest", @@ -75,13 +75,14 @@ "@babel/preset-env": "^7.12.11", "@babel/preset-typescript": "^7.12.7", "@babel/register": "^7.12.10", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.7.tgz", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz", "@types/bs58": "^4.0.1", + "@types/content-type": "^1.1.5", "@types/jest": "^26.0.20", "@types/node": "12", "@types/request": "^2.48.5", - "@typescript-eslint/eslint-plugin": "^4.17.0", - "@typescript-eslint/parser": "^4.17.0", + "@typescript-eslint/eslint-plugin": "^5.6.0", + "@typescript-eslint/parser": "^5.6.0", "allchange": "^1.0.6", "babel-jest": "^26.6.3", "babelify": "^10.0.0", @@ -100,7 +101,7 @@ "rimraf": "^3.0.2", "terser": "^5.5.1", "tsify": "^5.0.2", - "typescript": "^4.1.3" + "typescript": "^4.5.3" }, "jest": { "testEnvironment": "node", diff --git a/release.sh b/release.sh index 6754f18bb29..4550d7b7a63 100755 --- a/release.sh +++ b/release.sh @@ -255,6 +255,12 @@ if [ -n "$signing_id" ]; then # the easiest way to check the validity of the tarball from git is to unzip # it and compare it with our own idea of what the tar should look like. + # This uses git archive which seems to be what github uses. Specifically, + # the header fields are set in the same way: same file mode, uid & gid + # both zero and mtime set to the timestamp of the commit that the tag + # references. Also note that this puts the commit into the tar headers + # and can be extracted with gunzip -c foo.tar.gz | git get-tar-commit-id + # the name of the sig file we want to create source_sigfile="${tag}-src.tar.gz.asc" diff --git a/spec/integ/matrix-client-event-timeline.spec.js b/spec/integ/matrix-client-event-timeline.spec.js index c8ec42c74c7..199f480771f 100644 --- a/spec/integ/matrix-client-event-timeline.spec.js +++ b/spec/integ/matrix-client-event-timeline.spec.js @@ -502,7 +502,7 @@ describe("MatrixClient event timelines", function() { const params = req.queryParams; expect(params.dir).toEqual("b"); expect(params.from).toEqual("start_token0"); - expect(params.limit).toEqual(30); + expect(params.limit).toEqual("30"); }).respond(200, function() { return { chunk: [EVENTS[1], EVENTS[2]], @@ -553,7 +553,7 @@ describe("MatrixClient event timelines", function() { const params = req.queryParams; expect(params.dir).toEqual("f"); expect(params.from).toEqual("end_token0"); - expect(params.limit).toEqual(20); + expect(params.limit).toEqual("20"); }).respond(200, function() { return { chunk: [EVENTS[1], EVENTS[2]], diff --git a/spec/unit/models/MSC3089Branch.spec.ts b/spec/unit/models/MSC3089Branch.spec.ts index d782a4276e0..1daf1c07fde 100644 --- a/spec/unit/models/MSC3089Branch.spec.ts +++ b/spec/unit/models/MSC3089Branch.spec.ts @@ -312,7 +312,7 @@ describe("MSC3089Branch", () => { } as MatrixEvent); const events = [await branch.getFileEvent(), await branch2.getFileEvent(), { - replacingEventId: () => null, + replacingEventId: (): string => null, getId: () => "$unknown", }]; staticRoom.getLiveTimeline = () => ({ getEvents: () => events }) as EventTimeline; diff --git a/spec/unit/utils.spec.ts b/spec/unit/utils.spec.ts index 61d11b1cf60..cd9b1b5b5e6 100644 --- a/spec/unit/utils.spec.ts +++ b/spec/unit/utils.spec.ts @@ -26,6 +26,15 @@ describe("utils", function() { "foo=bar&baz=beer%40", ); }); + + it("should handle boolean and numeric values", function() { + const params = { + string: "foobar", + number: 12345, + boolean: false, + }; + expect(utils.encodeParams(params)).toEqual("string=foobar&number=12345&boolean=false"); + }); }); describe("encodeUri", function() { diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 0a857b4e8f6..c7e65ff77d1 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -62,12 +62,6 @@ declare global { }; } - interface HTMLAudioElement { - // sinkId & setSinkId are experimental and typescript doesn't know about them - sinkId: string; - setSinkId(outputId: string); - } - interface DummyInterfaceWeShouldntBeUsingThis {} interface Navigator { diff --git a/src/@types/polls.ts b/src/@types/polls.ts new file mode 100644 index 00000000000..e030278572f --- /dev/null +++ b/src/@types/polls.ts @@ -0,0 +1,92 @@ +/* +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 { UnstableValue } from "../NamespacedValue"; +import { IContent } from "../models/event"; + +export const POLL_START_EVENT_TYPE = new UnstableValue( + "m.poll.start", "org.matrix.msc3381.poll.start"); + +export const POLL_RESPONSE_EVENT_TYPE = new UnstableValue( + "m.poll.response", "org.matrix.msc3381.poll.response"); + +export const POLL_END_EVENT_TYPE = new UnstableValue( + "m.poll.end", "org.matrix.msc3381.poll.end"); + +export const POLL_KIND_DISCLOSED = new UnstableValue( + "m.poll.disclosed", "org.matrix.msc3381.poll.disclosed"); + +export const POLL_KIND_UNDISCLOSED = new UnstableValue( + "m.poll.undisclosed", "org.matrix.msc3381.poll.undisclosed"); + +// TODO: [TravisR] Use extensible events library when ready +export const TEXT_NODE_TYPE = new UnstableValue("m.text", "org.matrix.msc1767.text"); + +export interface IPollAnswer extends IContent { + id: string; + [TEXT_NODE_TYPE.name]: string; +} + +export interface IPollContent extends IContent { + [POLL_START_EVENT_TYPE.name]: { + kind: string; // disclosed or undisclosed (untypeable for now) + question: { + [TEXT_NODE_TYPE.name]: string; + }; + answers: IPollAnswer[]; + }; + [TEXT_NODE_TYPE.name]: string; +} + +export interface IPollResponseContent extends IContent { + [POLL_RESPONSE_EVENT_TYPE.name]: { + answers: string[]; + }; + "m.relates_to": { + "event_id": string; + "rel_type": string; + }; +} + +export interface IPollEndContent extends IContent { + [POLL_END_EVENT_TYPE.name]: {}; + "m.relates_to": { + "event_id": string; + "rel_type": string; + }; +} + +export function makePollContent( + question: string, + answers: string[], + kind: string, +): IPollContent { + question = question.trim(); + answers = answers.map(a => a.trim()).filter(a => !!a); + return { + [TEXT_NODE_TYPE.name]: + `${question}\n${answers.map((a, i) => `${i + 1}. ${a}`).join('\n')}`, + [POLL_START_EVENT_TYPE.name]: { + kind: kind, + question: { + [TEXT_NODE_TYPE.name]: question, + }, + answers: answers.map( + (a, i) => ({ id: `${i}-${a}`, [TEXT_NODE_TYPE.name]: a }), + ), + }, + }; +} diff --git a/src/@types/spaces.ts b/src/@types/spaces.ts index 088864bd46b..7ca55c39e1b 100644 --- a/src/@types/spaces.ts +++ b/src/@types/spaces.ts @@ -44,7 +44,6 @@ export interface ISpaceSummaryEvent { } export interface IHierarchyRelation extends IStrippedState { - room_id: string; origin_server_ts: number; content: { order?: string; diff --git a/src/client.ts b/src/client.ts index 9a95f1b1101..48ad693169c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -20,7 +20,6 @@ limitations under the License. */ import { EventEmitter } from "events"; -import { ReadStream } from "fs"; import { ISyncStateData, SyncApi } from "./sync"; import { EventStatus, IContent, IDecryptOptions, IEvent, MatrixEvent } from "./models/event"; @@ -42,13 +41,19 @@ import { IRoomEncryption, RoomList } from './crypto/RoomList'; import { logger } from './logger'; import { SERVICE_TYPES } from './service-types'; import { + FileType, + IHttpOpts, + IUpload, MatrixError, MatrixHttpApi, + Method, PREFIX_IDENTITY_V2, PREFIX_MEDIA_R0, PREFIX_R0, + PREFIX_V1, PREFIX_UNSTABLE, retryNetworkOperation, + UploadContentResponseType, } from "./http-api"; import { Crypto, @@ -81,7 +86,6 @@ import { IKeyBackupSession, } from "./crypto/keybackup"; import { IIdentityServerProvider } from "./@types/IIdentityServerProvider"; -import type Request from "request"; import { MatrixScheduler } from "./scheduler"; import { IAuthData, ICryptoCallbacks, IMinimalEvent, IRoomEvent, IStateEvent, NotificationCountType } from "./matrix"; import { @@ -155,7 +159,7 @@ import { Thread, ThreadEvent } from "./models/thread"; export type Store = IStore; export type SessionStore = WebStorageSessionStore; -export type Callback = (err: Error | any | null, data?: any) => void; +export type Callback = (err: Error | any | null, data?: T) => void; export type ResetTimelineCallback = (roomId: string) => boolean; const SCROLLBACK_DELAY_MS = 3000; @@ -208,7 +212,7 @@ export interface ICreateClientOpts { * as it returns a function which meets the required interface. See * {@link requestFunction} for more information. */ - request?: Request; + request?: IHttpOpts["request"]; userId?: string; @@ -257,7 +261,7 @@ export interface ICreateClientOpts { * to all requests with this client. Useful for application services which require * ?user_id=. */ - queryParams?: Record; + queryParams?: Record; /** * Device data exported with @@ -622,7 +626,7 @@ export interface IMyDevice { last_seen_ts?: number; } -interface IDownloadKeyResult { +export interface IDownloadKeyResult { failures: { [serverName: string]: object }; device_keys: { [userId: string]: { @@ -633,13 +637,42 @@ interface IDownloadKeyResult { }; }; }; + // the following three fields were added in 1.1 + master_keys?: { + [userId: string]: { + keys: { [keyId: string]: string }; + usage: string[]; + user_id: string; + }; + }; + self_signing_keys?: { + [userId: string]: { + keys: { [keyId: string]: string }; + signatures: ISignatures; + usage: string[]; + user_id: string; + }; + }; + user_signing_keys?: { + [userId: string]: { + keys: { [keyId: string]: string }; + signatures: ISignatures; + usage: string[]; + user_id: string; + }; + }; } -interface IClaimOTKsResult { +export interface IClaimOTKsResult { failures: { [serverName: string]: object }; one_time_keys: { [userId: string]: { - [deviceId: string]: string; + [deviceId: string]: { + [keyId: string]: { + key: string; + signatures: ISignatures; + }; + }; }; }; } @@ -697,6 +730,16 @@ export interface IProcessEventsOpts { initialSync?: boolean; } +interface IRoomHierarchy { + rooms: IHierarchyRoom[]; + next_batch?: string; +} + +interface ITimestampToEventResponse { + event_id: string; + origin_server_ts: string; +} + /* eslint-enable camelcase */ // We're using this constant for methods overloading and inspect whether a variable @@ -1091,9 +1134,9 @@ export class MatrixClient extends EventEmitter { account.unpickle(key, deviceData.account); logger.log("unpickled device"); - const rehydrateResult = await this.http.authedRequest( + const rehydrateResult = await this.http.authedRequest<{ success: boolean }>( undefined, - "POST", + Method.Post, "/dehydrated_device/claim", undefined, { @@ -1132,9 +1175,9 @@ export class MatrixClient extends EventEmitter { */ public async getDehydratedDevice(): Promise { try { - return await this.http.authedRequest( + return await this.http.authedRequest( undefined, - "GET", + Method.Get, "/dehydrated_device", undefined, undefined, { @@ -1167,10 +1210,7 @@ export class MatrixClient extends EventEmitter { logger.warn('not dehydrating device if crypto is not enabled'); return; } - // XXX: Private member access. - return await this.crypto.dehydrationManager.setKeyAndQueueDehydration( - key, keyInfo, deviceDisplayName, - ); + return await this.crypto.dehydrationManager.setKeyAndQueueDehydration(key, keyInfo, deviceDisplayName); } /** @@ -1428,14 +1468,12 @@ export class MatrixClient extends EventEmitter { } } - // We swallow errors because we need a default object anyhow return this.http.authedRequest( - undefined, "GET", "/capabilities", - ).catch((e: Error) => { + undefined, Method.Get, "/capabilities", + ).catch((e: Error): void => { + // We swallow errors because we need a default object anyhow logger.error(e); - return null; // otherwise consume the error - }).then((r) => { - if (!r) r = {}; + }).then((r: { capabilities?: ICapabilities } = {}) => { const capabilities: ICapabilities = r["capabilities"] || {}; // If the capabilities missed the cache, cache it for a shorter amount @@ -2433,7 +2471,7 @@ export class MatrixClient extends EventEmitter { let res; try { res = await this.http.authedRequest( - undefined, "GET", "/room_keys/version", undefined, undefined, + undefined, Method.Get, "/room_keys/version", undefined, undefined, { prefix: PREFIX_UNSTABLE }, ); } catch (e) { @@ -2559,7 +2597,6 @@ export class MatrixClient extends EventEmitter { * @param {object} info Info object from prepareKeyBackupVersion * @returns {Promise} Object with 'version' param indicating the version created */ - // TODO: Fix types public async createKeyBackupVersion(info: IKeyBackupInfo): Promise { if (!this.crypto) { throw new Error("End-to-end encryption disabled"); @@ -2591,8 +2628,8 @@ export class MatrixClient extends EventEmitter { await this.crypto.crossSigningInfo.signObject(data.auth_data, "master"); } - const res = await this.http.authedRequest( - undefined, "POST", "/room_keys/version", undefined, data, + const res = await this.http.authedRequest( + undefined, Method.Post, "/room_keys/version", undefined, data, { prefix: PREFIX_UNSTABLE }, ); @@ -2624,7 +2661,7 @@ export class MatrixClient extends EventEmitter { }); return this.http.authedRequest( - undefined, "DELETE", path, undefined, undefined, + undefined, Method.Delete, path, undefined, undefined, { prefix: PREFIX_UNSTABLE }, ); } @@ -2663,7 +2700,7 @@ export class MatrixClient extends EventEmitter { const path = this.makeKeyBackupPath(roomId, sessionId, version); return this.http.authedRequest( - undefined, "PUT", path.path, path.queryData, data, + undefined, Method.Put, path.path, path.queryData, data, { prefix: PREFIX_UNSTABLE }, ); } @@ -2843,7 +2880,7 @@ export class MatrixClient extends EventEmitter { } let totalKeyCount = 0; - let keys = []; + let keys: IMegolmSessionData[] = []; const path = this.makeKeyBackupPath(targetRoomId, targetSessionId, backupInfo.version); @@ -2873,7 +2910,7 @@ export class MatrixClient extends EventEmitter { } const res = await this.http.authedRequest( - undefined, "GET", path.path, path.queryData, undefined, + undefined, Method.Get, path.path, path.queryData, undefined, { prefix: PREFIX_UNSTABLE }, ) as IRoomsKeysResponse | IRoomKeysResponse | IKeyBackupSession; @@ -2931,7 +2968,7 @@ export class MatrixClient extends EventEmitter { const path = this.makeKeyBackupPath(roomId, sessionId, version); return this.http.authedRequest( - undefined, "DELETE", path.path, path.queryData, undefined, + undefined, Method.Delete, path.path, path.queryData, undefined, { prefix: PREFIX_UNSTABLE }, ); } @@ -2998,7 +3035,7 @@ export class MatrixClient extends EventEmitter { */ public getMediaConfig(callback?: Callback): Promise { return this.http.authedRequest( - callback, "GET", "/config", undefined, undefined, { + callback, Method.Get, "/config", undefined, undefined, { prefix: PREFIX_MEDIA_R0, }, ); @@ -3088,7 +3125,7 @@ export class MatrixClient extends EventEmitter { $type: eventType, }); const promise = retryNetworkOperation(5, () => { - return this.http.authedRequest(undefined, "PUT", path, undefined, content); + return this.http.authedRequest(undefined, Method.Put, path, undefined, content); }); if (callback) { promise.then(result => callback(null, result), callback); @@ -3130,7 +3167,7 @@ export class MatrixClient extends EventEmitter { }); try { return await this.http.authedRequest( - undefined, "GET", path, undefined, + undefined, Method.Get, path, undefined, ); } catch (e) { if (e.data && e.data.errcode === 'M_NOT_FOUND') { @@ -3204,7 +3241,7 @@ export class MatrixClient extends EventEmitter { if (opts.inviteSignUrl) { signPromise = this.http.requestOtherUrl( - undefined, 'POST', + undefined, Method.Post, opts.inviteSignUrl, { mxid: this.credentials.userId }, ); } @@ -3224,7 +3261,7 @@ export class MatrixClient extends EventEmitter { } const path = utils.encodeUri("/join/$roomid", { $roomid: roomIdOrAlias }); - const res = await this.http.authedRequest(undefined, "POST", path, queryString, data, reqOpts); + const res = await this.http.authedRequest(undefined, Method.Post, path, queryString, data, reqOpts); const roomId = res['room_id']; const syncApi = new SyncApi(this, this.clientOpts); @@ -3310,7 +3347,7 @@ export class MatrixClient extends EventEmitter { $roomId: roomId, }); return this.http.authedRequest( - callback, "GET", path, undefined, + callback, Method.Get, path, undefined, ); } @@ -3328,7 +3365,7 @@ export class MatrixClient extends EventEmitter { $roomId: roomId, $tag: tagName, }); - return this.http.authedRequest(callback, "PUT", path, undefined, metadata); + return this.http.authedRequest(callback, Method.Put, path, undefined, metadata); } /** @@ -3344,9 +3381,7 @@ export class MatrixClient extends EventEmitter { $roomId: roomId, $tag: tagName, }); - return this.http.authedRequest( - callback, "DELETE", path, undefined, undefined, - ); + return this.http.authedRequest(callback, Method.Delete, path, undefined, undefined); } /** @@ -3368,7 +3403,7 @@ export class MatrixClient extends EventEmitter { $roomId: roomId, $type: eventType, }); - return this.http.authedRequest(callback, "PUT", path, undefined, content); + return this.http.authedRequest(callback, Method.Put, path, undefined, content); } /** @@ -3400,7 +3435,7 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/rooms/$roomId/state/m.room.power_levels", { $roomId: roomId, }); - return this.http.authedRequest(callback, "PUT", path, undefined, content); + return this.http.authedRequest(callback, Method.Put, path, undefined, content); } /** @@ -3683,8 +3718,8 @@ export class MatrixClient extends EventEmitter { path = utils.encodeUri("/rooms/$roomId/send/$eventType/$txnId", pathParams); } - return this.http.authedRequest( - undefined, "PUT", path, undefined, event.getWireContent(), + return this.http.authedRequest( + undefined, Method.Put, path, undefined, event.getWireContent(), ).then((res) => { logger.log(`Event sent to ${event.getRoomId()} with event id ${res.event_id}`); return res; @@ -4149,7 +4184,7 @@ export class MatrixClient extends EventEmitter { $receiptType: receiptType, $eventId: event.getId(), }); - const promise = this.http.authedRequest(callback, "POST", path, undefined, body || {}); + const promise = this.http.authedRequest(callback, Method.Post, path, undefined, body || {}); const room = this.getRoom(event.getRoomId()); if (room) { @@ -4266,9 +4301,9 @@ export class MatrixClient extends EventEmitter { } const resp = this.http.authedRequest( - callback, "GET", "/preview_url", { - url: url, - ts: ts, + callback, Method.Get, "/preview_url", { + url, + ts: ts.toString(), }, undefined, { prefix: PREFIX_MEDIA_R0, }, @@ -4301,7 +4336,7 @@ export class MatrixClient extends EventEmitter { if (isTyping) { data.timeout = timeoutMs ? timeoutMs : 20000; } - return this.http.authedRequest(callback, "PUT", path, undefined, data); + return this.http.authedRequest(callback, Method.Put, path, undefined, data); } /** @@ -4445,7 +4480,7 @@ export class MatrixClient extends EventEmitter { } } - return this.http.authedRequest(callback, "POST", path, undefined, params); + return this.http.authedRequest(callback, Method.Post, path, undefined, params); } /** @@ -4561,7 +4596,7 @@ export class MatrixClient extends EventEmitter { user_id: userId, }; return this.http.authedRequest( - callback, "POST", path, undefined, data, + callback, Method.Post, path, undefined, data, ); } @@ -4582,7 +4617,7 @@ export class MatrixClient extends EventEmitter { reason: reason, }; return this.http.authedRequest( - callback, "POST", path, undefined, data, + callback, Method.Post, path, undefined, data, ); } @@ -4614,7 +4649,7 @@ export class MatrixClient extends EventEmitter { { $roomId: roomId, $userId: userId }, ); - return this.http.authedRequest(callback, "PUT", path, undefined, { + return this.http.authedRequest(callback, Method.Put, path, undefined, { membership: membershipValue, reason: reason, }); @@ -4637,7 +4672,7 @@ export class MatrixClient extends EventEmitter { $membership: membership, }); return this.http.authedRequest( - callback, "POST", path, undefined, { + callback, Method.Post, path, undefined, { user_id: userId, // may be undefined e.g. on leave reason: reason, }, @@ -4674,7 +4709,7 @@ export class MatrixClient extends EventEmitter { $userId: this.credentials.userId, $info: info, }); - return this.http.authedRequest(callback, "PUT", path, undefined, data); + return this.http.authedRequest(callback, Method.Put, path, undefined, data); } /** @@ -4777,7 +4812,7 @@ export class MatrixClient extends EventEmitter { throw new Error("Bad presence value: " + opts.presence); } return this.http.authedRequest( - callback, "PUT", path, undefined, opts, + callback, Method.Put, path, undefined, opts, ); } @@ -4792,7 +4827,7 @@ export class MatrixClient extends EventEmitter { $userId: userId, }); - return this.http.authedRequest(callback, "GET", path, undefined, undefined); + return this.http.authedRequest(callback, Method.Get, path, undefined, undefined); } /** @@ -4938,7 +4973,7 @@ export class MatrixClient extends EventEmitter { // TODO: we should implement a backoff (as per scrollback()) to deal more // nicely with HTTP errors. - const promise = this.http.authedRequest(undefined, "GET", path, params).then((res) => { + const promise = this.http.authedRequest(undefined, Method.Get, path, params).then((res) => { // TODO types if (!res.event) { throw new Error("'event' not in '/context' result - homeserver too old?"); } @@ -4999,18 +5034,16 @@ export class MatrixClient extends EventEmitter { public createMessagesRequest( roomId: string, fromToken: string, - limit: number, + limit = 30, dir: Direction, timelineFilter?: Filter, ): Promise { const path = utils.encodeUri("/rooms/$roomId/messages", { $roomId: roomId }); - if (limit === undefined) { - limit = 30; - } - const params: Record = { + + const params: Record = { from: fromToken, - limit: limit, - dir: dir, + limit: limit.toString(), + dir, }; let filter = null; @@ -5028,7 +5061,7 @@ export class MatrixClient extends EventEmitter { if (filter) { params.filter = JSON.stringify(filter); } - return this.http.authedRequest(undefined, "GET", path, params); + return this.http.authedRequest(undefined, Method.Get, path, params); } /** @@ -5088,8 +5121,8 @@ export class MatrixClient extends EventEmitter { params.from = token; } - promise = this.http.authedRequest( - undefined, "GET", path, params, undefined, + promise = this.http.authedRequest( // TODO types + undefined, Method.Get, path, params, undefined, ).then((res) => { const token = res.next_token; const matrixEvents = []; @@ -5481,7 +5514,7 @@ export class MatrixClient extends EventEmitter { } } - return this.http.request(undefined, "POST", endpoint, undefined, postParams); + return this.http.request(undefined, Method.Post, endpoint, undefined, postParams); } /** @@ -5699,18 +5732,14 @@ export class MatrixClient extends EventEmitter { searchResults.count = roomEvents.count; searchResults.next_batch = roomEvents.next_batch; - // combine the highlight list with our existing list; build an object - // to avoid O(N^2) fail - const highlights = {}; - roomEvents.highlights.forEach((hl) => { - highlights[hl] = 1; - }); + // combine the highlight list with our existing list; + const highlights = new Set(roomEvents.highlights); searchResults.highlights.forEach((hl) => { - highlights[hl] = 1; + highlights.add(hl); }); // turn it back into a list. - searchResults.highlights = Object.keys(highlights); + searchResults.highlights = Array.from(highlights); // append the new results to our existing results const resultsLength = roomEvents.results ? roomEvents.results.length : 0; @@ -5767,7 +5796,8 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/user/$userId/filter", { $userId: this.credentials.userId, }); - return this.http.authedRequest(undefined, "POST", path, undefined, content).then((response) => { + // TODO types + return this.http.authedRequest(undefined, Method.Post, path, undefined, content).then((response) => { // persist the filter const filter = Filter.fromJson( this.credentials.userId, response.filter_id, content, @@ -5799,13 +5829,11 @@ export class MatrixClient extends EventEmitter { $filterId: filterId, }); - return this.http.authedRequest( - undefined, "GET", path, undefined, undefined, + return this.http.authedRequest( + undefined, Method.Get, path, undefined, undefined, ).then((response) => { // persist the filter - const filter = Filter.fromJson( - userId, filterId, response, - ); + const filter = Filter.fromJson(userId, filterId, response); this.store.storeFilter(filter); return filter; }); @@ -5879,7 +5907,7 @@ export class MatrixClient extends EventEmitter { }); return this.http.authedRequest( - undefined, "POST", path, undefined, {}, + undefined, Method.Post, path, undefined, {}, ); } @@ -5896,7 +5924,7 @@ export class MatrixClient extends EventEmitter { * @return {module:http-api.MatrixError} Rejects: with an error response. */ public turnServer(callback?: Callback): Promise { - return this.http.authedRequest(callback, "GET", "/voip/turnServer"); + return this.http.authedRequest(callback, Method.Get, "/voip/turnServer"); } /** @@ -5994,7 +6022,7 @@ export class MatrixClient extends EventEmitter { { $userId: this.getUserId() }, ); return this.http.authedRequest( - undefined, 'GET', path, undefined, undefined, { prefix: '' }, + undefined, Method.Get, path, undefined, undefined, { prefix: '' }, ).then(r => r['admin']); // pull out the specific boolean we want } @@ -6010,7 +6038,7 @@ export class MatrixClient extends EventEmitter { "/_synapse/admin/v1/whois/$userId", { $userId: userId }, ); - return this.http.authedRequest(undefined, 'GET', path, undefined, undefined, { prefix: '' }); + return this.http.authedRequest(undefined, Method.Get, path, undefined, undefined, { prefix: '' }); } /** @@ -6025,7 +6053,7 @@ export class MatrixClient extends EventEmitter { { $userId: userId }, ); return this.http.authedRequest( - undefined, 'POST', path, undefined, undefined, { prefix: '' }, + undefined, Method.Post, path, undefined, undefined, { prefix: '' }, ); } @@ -6078,8 +6106,8 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/uk.half-shot.msc2666/user/shared_rooms/$userId", { $userId: userId, }); - const res = await this.http.authedRequest( - undefined, "GET", path, undefined, undefined, + const res = await this.http.authedRequest<{ joined: string[] }>( + undefined, Method.Get, path, undefined, undefined, { prefix: PREFIX_UNSTABLE }, ); return res.joined; @@ -6095,15 +6123,15 @@ export class MatrixClient extends EventEmitter { return this.serverVersionsPromise; } - this.serverVersionsPromise = this.http.request( + this.serverVersionsPromise = this.http.request( undefined, // callback - "GET", "/_matrix/client/versions", + Method.Get, "/_matrix/client/versions", undefined, // queryParams undefined, // data { prefix: '', }, - ).catch((e) => { + ).catch((e: Error) => { // Need to unset this if it fails, otherwise we'll never retry this.serverVersionsPromise = null; // but rethrow the exception to anything that was waiting @@ -6116,7 +6144,7 @@ export class MatrixClient extends EventEmitter { /** * Check if a particular spec version is supported by the server. * @param {string} version The spec version (such as "r0.5.0") to check for. - * @return {Promise} Whether it is supported + * @return {Promise} Whether it is supported */ public async isVersionSupported(version: string): Promise { const { versions } = await this.getVersions(); @@ -6124,7 +6152,7 @@ export class MatrixClient extends EventEmitter { } /** - * Query the server to see if it support members lazy loading + * Query the server to see if it supports members lazy loading * @return {Promise} true if server supports lazy loading */ public async doesServerSupportLazyLoading(): Promise { @@ -6423,8 +6451,8 @@ export class MatrixClient extends EventEmitter { * @return {Promise} Resolves: to `true`. */ public isUsernameAvailable(username: string): Promise { - return this.http.authedRequest( - undefined, "GET", '/register/available', { username: username }, + return this.http.authedRequest<{ available: true }>( + undefined, Method.Get, '/register/available', { username: username }, ).then((response) => { return response.available; }); @@ -6545,7 +6573,7 @@ export class MatrixClient extends EventEmitter { params.kind = kind; } - return this.http.request(callback, "POST", "/register", params, data); + return this.http.request(callback, Method.Post, "/register", params, data); } /** @@ -6554,7 +6582,7 @@ export class MatrixClient extends EventEmitter { * @return {module:http-api.MatrixError} Rejects: with an error response. */ public loginFlows(callback?: Callback): Promise { // TODO: Types - return this.http.request(callback, "GET", "/login"); + return this.http.request(callback, Method.Get, "/login"); } /** @@ -6584,7 +6612,7 @@ export class MatrixClient extends EventEmitter { if (callback) { callback(error, response); } - }, "POST", "/login", undefined, loginData, + }, Method.Post, "/login", undefined, loginData, ); } @@ -6663,7 +6691,7 @@ export class MatrixClient extends EventEmitter { */ public logout(callback?: Callback): Promise<{}> { return this.http.authedRequest( - callback, "POST", '/logout', + callback, Method.Post, '/logout', ); } @@ -6692,7 +6720,7 @@ export class MatrixClient extends EventEmitter { body.erase = erase; } - return this.http.authedRequest(undefined, "POST", '/account/deactivate', undefined, body); + return this.http.authedRequest(undefined, Method.Post, '/account/deactivate', undefined, body); } /** @@ -6749,7 +6777,7 @@ export class MatrixClient extends EventEmitter { } } - return this.http.authedRequest(callback, "POST", "/createRoom", undefined, options); + return this.http.authedRequest(callback, Method.Post, "/createRoom", undefined, options); } /** @@ -6768,11 +6796,7 @@ export class MatrixClient extends EventEmitter { eventType?: EventType | string | null, opts: IRelationsRequestOpts = {}, ): Promise { - const params = new URLSearchParams(); - for (const [key, val] of Object.entries(opts)) { - params.set(key, val); - } - const queryString = params.toString(); + const queryString = utils.encodeParams(opts as Record); let templatedUrl = "/rooms/$roomId/relations/$eventId"; if (relationType !== null) templatedUrl += "/$relationType"; @@ -6786,7 +6810,7 @@ export class MatrixClient extends EventEmitter { $eventType: eventType, }); return await this.http.authedRequest( - undefined, "GET", path, null, null, { + undefined, Method.Get, path, null, null, { prefix: PREFIX_UNSTABLE, }, ); @@ -6800,7 +6824,7 @@ export class MatrixClient extends EventEmitter { */ public roomState(roomId: string, callback?: Callback): Promise { const path = utils.encodeUri("/rooms/$roomId/state", { $roomId: roomId }); - return this.http.authedRequest(callback, "GET", path); + return this.http.authedRequest(callback, Method.Get, path); } /** @@ -6823,7 +6847,7 @@ export class MatrixClient extends EventEmitter { $eventId: eventId, }, ); - return this.http.authedRequest(callback, "GET", path); + return this.http.authedRequest(callback, Method.Get, path); } /** @@ -6857,7 +6881,7 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/rooms/$roomId/members?" + queryString, { $roomId: roomId }); - return this.http.authedRequest(callback, "GET", path); + return this.http.authedRequest(callback, Method.Get, path); } /** @@ -6873,7 +6897,7 @@ export class MatrixClient extends EventEmitter { ): Promise<{ replacement_room: string }> { // eslint-disable-line camelcase const path = utils.encodeUri("/rooms/$roomId/upgrade", { $roomId: roomId }); return this.http.authedRequest( - undefined, "POST", path, undefined, { new_version: newVersion }, + undefined, Method.Post, path, undefined, { new_version: newVersion }, ); } @@ -6902,7 +6926,7 @@ export class MatrixClient extends EventEmitter { path = utils.encodeUri(path + "/$stateKey", pathParams); } return this.http.authedRequest( - callback, "GET", path, + callback, Method.Get, path, ); } @@ -6931,7 +6955,7 @@ export class MatrixClient extends EventEmitter { if (stateKey !== undefined) { path = utils.encodeUri(path + "/$stateKey", pathParams); } - return this.http.authedRequest(callback, "PUT", path, undefined, content); + return this.http.authedRequest(callback, Method.Put, path, undefined, content); } /** @@ -6946,15 +6970,12 @@ export class MatrixClient extends EventEmitter { callback = limit as any as Callback; // legacy limit = undefined; } + const path = utils.encodeUri("/rooms/$roomId/initialSync", { $roomId: roomId }, ); - if (!limit) { - limit = 30; - } - return this.http.authedRequest( - callback, "GET", path, { limit: limit }, - ); + + return this.http.authedRequest(callback, Method.Get, path, { limit: limit?.toString() ?? "30" }); } /** @@ -6988,7 +7009,7 @@ export class MatrixClient extends EventEmitter { "org.matrix.msc2285.hidden": Boolean(opts ? opts.hidden : false), }; - return this.http.authedRequest(undefined, "POST", path, undefined, content); + return this.http.authedRequest(undefined, Method.Post, path, undefined, content); } /** @@ -6997,7 +7018,7 @@ export class MatrixClient extends EventEmitter { */ public getJoinedRooms(): Promise { const path = utils.encodeUri("/joined_rooms", {}); - return this.http.authedRequest(undefined, "GET", path); + return this.http.authedRequest(undefined, Method.Get, path); } /** @@ -7011,7 +7032,7 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/rooms/$roomId/joined_members", { $roomId: roomId, }); - return this.http.authedRequest(undefined, "GET", path); + return this.http.authedRequest(undefined, Method.Get, path); } /** @@ -7043,9 +7064,9 @@ export class MatrixClient extends EventEmitter { } if (Object.keys(options).length === 0 && Object.keys(queryParams).length === 0) { - return this.http.authedRequest(callback, "GET", "/publicRooms"); + return this.http.authedRequest(callback, Method.Get, "/publicRooms"); } else { - return this.http.authedRequest(callback, "POST", "/publicRooms", queryParams, options); + return this.http.authedRequest(callback, Method.Post, "/publicRooms", queryParams, options); } } @@ -7064,7 +7085,7 @@ export class MatrixClient extends EventEmitter { const data = { room_id: roomId, }; - return this.http.authedRequest(callback, "PUT", path, undefined, data); + return this.http.authedRequest(callback, Method.Put, path, undefined, data); } /** @@ -7079,7 +7100,7 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/directory/room/$alias", { $alias: alias, }); - return this.http.authedRequest(callback, "DELETE", path, undefined, undefined); + return this.http.authedRequest(callback, Method.Delete, path, undefined, undefined); } /** @@ -7092,7 +7113,7 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/rooms/$roomId/aliases", { $roomId: roomId }); const prefix = PREFIX_UNSTABLE + "/org.matrix.msc2432"; - return this.http.authedRequest(callback, "GET", path, null, null, { prefix }); + return this.http.authedRequest(callback, Method.Get, path, null, null, { prefix }); } /** @@ -7110,7 +7131,7 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/directory/room/$alias", { $alias: alias, }); - return this.http.authedRequest(callback, "GET", path); + return this.http.authedRequest(callback, Method.Get, path); } /** @@ -7123,7 +7144,7 @@ export class MatrixClient extends EventEmitter { public resolveRoomAlias(roomAlias: string, callback?: Callback): Promise<{ room_id: string, servers: string[] }> { // TODO: deprecate this or getRoomIdForAlias const path = utils.encodeUri("/directory/room/$alias", { $alias: roomAlias }); - return this.http.request(callback, "GET", path); + return this.http.request(callback, Method.Get, path); } /** @@ -7137,7 +7158,7 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/directory/list/room/$roomId", { $roomId: roomId, }); - return this.http.authedRequest(callback, "GET", path); + return this.http.authedRequest(callback, Method.Get, path); } /** @@ -7154,7 +7175,7 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/directory/list/room/$roomId", { $roomId: roomId, }); - return this.http.authedRequest(callback, "PUT", path, undefined, { visibility }); + return this.http.authedRequest(callback, Method.Put, path, undefined, { visibility }); } /** @@ -7181,7 +7202,7 @@ export class MatrixClient extends EventEmitter { $roomId: roomId, }); return this.http.authedRequest( - callback, "PUT", path, undefined, { "visibility": visibility }, + callback, Method.Put, path, undefined, { "visibility": visibility }, ); } @@ -7202,7 +7223,7 @@ export class MatrixClient extends EventEmitter { body.limit = opts.limit; } - return this.http.authedRequest(undefined, "POST", "/user_directory/search", undefined, body); + return this.http.authedRequest(undefined, Method.Post, "/user_directory/search", undefined, body); } /** @@ -7245,11 +7266,11 @@ export class MatrixClient extends EventEmitter { * determined by this.opts.onlyData, opts.rawResponse, and * opts.onlyContentUri. Rejects with an error (usually a MatrixError). */ - public uploadContent( - file: File | String | Buffer | ReadStream | Blob, - opts?: IUploadOpts, - ): IAbortablePromise { // TODO: Advanced types - return this.http.uploadContent(file, opts); + public uploadContent( + file: FileType, + opts?: O, + ): IAbortablePromise> { + return this.http.uploadContent(file, opts); } /** @@ -7269,7 +7290,7 @@ export class MatrixClient extends EventEmitter { * - loaded: Number of bytes uploaded * - total: Total number of bytes to upload */ - public getCurrentUploads(): { promise: Promise, loaded: number, total: number }[] { // TODO: Advanced types (promise) + public getCurrentUploads(): IUpload[] { return this.http.getCurrentUploads(); } @@ -7297,7 +7318,7 @@ export class MatrixClient extends EventEmitter { { $userId: userId, $info: info }) : utils.encodeUri("/profile/$userId", { $userId: userId }); - return this.http.authedRequest(callback, "GET", path); + return this.http.authedRequest(callback, Method.Get, path); } /** @@ -7307,7 +7328,7 @@ export class MatrixClient extends EventEmitter { */ public getThreePids(callback?: Callback): Promise<{ threepids: IThreepid[] }> { const path = "/account/3pid"; - return this.http.authedRequest(callback, "GET", path, undefined, undefined); + return this.http.authedRequest(callback, Method.Get, path, undefined, undefined); } /** @@ -7330,7 +7351,7 @@ export class MatrixClient extends EventEmitter { 'bind': bind, }; return this.http.authedRequest( - callback, "POST", path, null, data, + callback, Method.Post, path, null, data, ); } @@ -7349,7 +7370,7 @@ export class MatrixClient extends EventEmitter { public async addThreePidOnly(data: IAddThreePidOnlyBody): Promise<{}> { const path = "/account/3pid/add"; const prefix = await this.isVersionSupported("r0.6.0") ? PREFIX_R0 : PREFIX_UNSTABLE; - return this.http.authedRequest(undefined, "POST", path, null, data, { prefix }); + return this.http.authedRequest(undefined, Method.Post, path, null, data, { prefix }); } /** @@ -7371,7 +7392,7 @@ export class MatrixClient extends EventEmitter { const prefix = await this.isVersionSupported("r0.6.0") ? PREFIX_R0 : PREFIX_UNSTABLE; return this.http.authedRequest( - undefined, "POST", path, null, data, { prefix }, + undefined, Method.Post, path, null, data, { prefix }, ); } @@ -7398,7 +7419,7 @@ export class MatrixClient extends EventEmitter { id_server: this.getIdentityServerUrl(true), }; const prefix = await this.isVersionSupported("r0.6.0") ? PREFIX_R0 : PREFIX_UNSTABLE; - return this.http.authedRequest(undefined, "POST", path, null, data, { prefix }); + return this.http.authedRequest(undefined, Method.Post, path, null, data, { prefix }); } /** @@ -7415,7 +7436,7 @@ export class MatrixClient extends EventEmitter { // eslint-disable-next-line camelcase ): Promise<{ id_server_unbind_result: IdServerUnbindResult }> { const path = "/account/3pid/delete"; - return this.http.authedRequest(undefined, "POST", path, null, { medium, address }); + return this.http.authedRequest(undefined, Method.Post, path, null, { medium, address }); } /** @@ -7434,7 +7455,7 @@ export class MatrixClient extends EventEmitter { }; return this.http.authedRequest( - callback, "POST", path, null, data, + callback, Method.Post, path, null, data, ); } @@ -7444,7 +7465,7 @@ export class MatrixClient extends EventEmitter { * @return {module:http-api.MatrixError} Rejects: with an error response. */ public getDevices(): Promise<{ devices: IMyDevice[] }> { - return this.http.authedRequest(undefined, 'GET', "/devices", undefined, undefined); + return this.http.authedRequest(undefined, Method.Get, "/devices", undefined, undefined); } /** @@ -7457,7 +7478,7 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/devices/$device_id", { $device_id: deviceId, }); - return this.http.authedRequest(undefined, 'GET', path, undefined, undefined); + return this.http.authedRequest(undefined, Method.Get, path, undefined, undefined); } /** @@ -7474,7 +7495,7 @@ export class MatrixClient extends EventEmitter { $device_id: deviceId, }); - return this.http.authedRequest(undefined, "PUT", path, undefined, body); + return this.http.authedRequest(undefined, Method.Put, path, undefined, body); } /** @@ -7496,7 +7517,7 @@ export class MatrixClient extends EventEmitter { body.auth = auth; } - return this.http.authedRequest(undefined, "DELETE", path, undefined, body); + return this.http.authedRequest(undefined, Method.Delete, path, undefined, body); } /** @@ -7515,7 +7536,7 @@ export class MatrixClient extends EventEmitter { } const path = "/delete_devices"; - return this.http.authedRequest(undefined, "POST", path, undefined, body); + return this.http.authedRequest(undefined, Method.Post, path, undefined, body); } /** @@ -7527,7 +7548,7 @@ export class MatrixClient extends EventEmitter { */ public getPushers(callback?: Callback): Promise<{ pushers: IPusher[] }> { const path = "/pushers"; - return this.http.authedRequest(callback, "GET", path, undefined, undefined); + return this.http.authedRequest(callback, Method.Get, path, undefined, undefined); } /** @@ -7540,7 +7561,7 @@ export class MatrixClient extends EventEmitter { */ public setPusher(pusher: IPusherRequest, callback?: Callback): Promise<{}> { const path = "/pushers/set"; - return this.http.authedRequest(callback, "POST", path, null, pusher); + return this.http.authedRequest(callback, Method.Post, path, null, pusher); } /** @@ -7550,7 +7571,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: IPushRules) => { + return this.http.authedRequest(callback, Method.Get, "/pushrules/").then((rules: IPushRules) => { return PushProcessor.rewriteDefaultRules(rules); }); } @@ -7576,7 +7597,7 @@ export class MatrixClient extends EventEmitter { $kind: kind, $ruleId: ruleId, }); - return this.http.authedRequest(callback, "PUT", path, undefined, body); + return this.http.authedRequest(callback, Method.Put, path, undefined, body); } /** @@ -7598,7 +7619,7 @@ export class MatrixClient extends EventEmitter { $kind: kind, $ruleId: ruleId, }); - return this.http.authedRequest(callback, "DELETE", path); + return this.http.authedRequest(callback, Method.Delete, path); } /** @@ -7623,7 +7644,7 @@ export class MatrixClient extends EventEmitter { $ruleId: ruleId, }); return this.http.authedRequest( - callback, "PUT", path, undefined, { "enabled": enabled }, + callback, Method.Put, path, undefined, { "enabled": enabled }, ); } @@ -7649,7 +7670,7 @@ export class MatrixClient extends EventEmitter { $ruleId: ruleId, }); return this.http.authedRequest( - callback, "PUT", path, undefined, { "actions": actions }, + callback, Method.Put, path, undefined, { "actions": actions }, ); } @@ -7670,7 +7691,7 @@ export class MatrixClient extends EventEmitter { if (opts.next_batch) { queryParams.next_batch = opts.next_batch; } - return this.http.authedRequest(callback, "POST", "/search", queryParams, opts.body); + return this.http.authedRequest(callback, Method.Post, "/search", queryParams, opts.body); } /** @@ -7691,12 +7712,12 @@ export class MatrixClient extends EventEmitter { opts?: void, callback?: Callback, ): Promise { - return this.http.authedRequest(callback, "POST", "/keys/upload", undefined, content); + return this.http.authedRequest(callback, Method.Post, "/keys/upload", undefined, content); } public uploadKeySignatures(content: KeySignatures): Promise { return this.http.authedRequest( - undefined, "POST", '/keys/signatures/upload', undefined, + undefined, Method.Post, '/keys/signatures/upload', undefined, content, { prefix: PREFIX_UNSTABLE, }, @@ -7733,7 +7754,7 @@ export class MatrixClient extends EventEmitter { content.device_keys[u] = []; }); - return this.http.authedRequest(undefined, "POST", "/keys/query", undefined, content); + return this.http.authedRequest(undefined, Method.Post, "/keys/query", undefined, content); } /** @@ -7750,11 +7771,11 @@ export class MatrixClient extends EventEmitter { * an error response ({@link module:http-api.MatrixError}). */ public claimOneTimeKeys( - devices: string[], + devices: [string, string][], keyAlgorithm = "signed_curve25519", timeout?: number, ): Promise { - const queries = {}; + const queries: Record> = {}; if (keyAlgorithm === undefined) { keyAlgorithm = "signed_curve25519"; @@ -7772,7 +7793,7 @@ export class MatrixClient extends EventEmitter { content.timeout = timeout; } const path = "/keys/claim"; - return this.http.authedRequest(undefined, "POST", path, undefined, content); + return this.http.authedRequest(undefined, Method.Post, path, undefined, content); } /** @@ -7792,14 +7813,14 @@ export class MatrixClient extends EventEmitter { }; const path = "/keys/changes"; - return this.http.authedRequest(undefined, "GET", path, qps, undefined); + return this.http.authedRequest(undefined, Method.Get, path, qps, undefined); } public uploadDeviceSigningKeys(auth?: IAuthData, keys?: CrossSigningKeys): Promise<{}> { const data = Object.assign({}, keys); if (auth) Object.assign(data, { auth }); return this.http.authedRequest( - undefined, "POST", "/keys/device_signing/upload", undefined, data, { + undefined, Method.Post, "/keys/device_signing/upload", undefined, data, { prefix: PREFIX_UNSTABLE, }, ); @@ -7825,7 +7846,7 @@ export class MatrixClient extends EventEmitter { const uri = this.idBaseUrl + PREFIX_IDENTITY_V2 + "/account/register"; return this.http.requestOtherUrl( - undefined, "POST", uri, + undefined, Method.Post, uri, null, hsOpenIdToken, ); } @@ -7865,12 +7886,12 @@ export class MatrixClient extends EventEmitter { const params = { client_secret: clientSecret, email: email, - send_attempt: sendAttempt, + send_attempt: sendAttempt?.toString(), next_link: nextLink, }; return await this.http.idServerRequest( - callback, "POST", "/validate/email/requestToken", + callback, Method.Post, "/validate/email/requestToken", params, PREFIX_IDENTITY_V2, identityAccessToken, ); } @@ -7915,12 +7936,12 @@ export class MatrixClient extends EventEmitter { client_secret: clientSecret, country: phoneCountry, phone_number: phoneNumber, - send_attempt: sendAttempt, + send_attempt: sendAttempt?.toString(), next_link: nextLink, }; return await this.http.idServerRequest( - callback, "POST", "/validate/msisdn/requestToken", + callback, Method.Post, "/validate/msisdn/requestToken", params, PREFIX_IDENTITY_V2, identityAccessToken, ); } @@ -7957,7 +7978,7 @@ export class MatrixClient extends EventEmitter { }; return await this.http.idServerRequest( - undefined, "POST", "/validate/msisdn/submitToken", + undefined, Method.Post, "/validate/msisdn/submitToken", params, PREFIX_IDENTITY_V2, identityAccessToken, ); } @@ -7993,7 +8014,7 @@ export class MatrixClient extends EventEmitter { }; return this.http.requestOtherUrl( - undefined, "POST", url, undefined, params, + undefined, Method.Post, url, undefined, params, ); } @@ -8005,7 +8026,7 @@ export class MatrixClient extends EventEmitter { */ public getIdentityHashDetails(identityAccessToken: string): Promise { // TODO: Types return this.http.idServerRequest( - undefined, "GET", "/hash_details", + undefined, Method.Get, "/hash_details", null, PREFIX_IDENTITY_V2, identityAccessToken, ); } @@ -8074,7 +8095,7 @@ export class MatrixClient extends EventEmitter { } const response = await this.http.idServerRequest( - undefined, "POST", "/lookup", + undefined, Method.Post, "/lookup", params, PREFIX_IDENTITY_V2, identityAccessToken, ); @@ -8192,7 +8213,7 @@ export class MatrixClient extends EventEmitter { */ public getIdentityAccount(identityAccessToken: string): Promise { // TODO: Types return this.http.idServerRequest( - undefined, "GET", "/account", + undefined, Method.Get, "/account", undefined, PREFIX_IDENTITY_V2, identityAccessToken, ); } @@ -8227,7 +8248,7 @@ export class MatrixClient extends EventEmitter { }, {}); logger.log(`PUT ${path}`, targets); - return this.http.authedRequest(undefined, "PUT", path, undefined, body); + return this.http.authedRequest(undefined, Method.Put, path, undefined, body); } /** @@ -8236,8 +8257,8 @@ export class MatrixClient extends EventEmitter { * @return {Promise} Resolves to the result object */ public getThirdpartyProtocols(): Promise<{ [protocol: string]: IProtocol }> { - return this.http.authedRequest( - undefined, "GET", "/thirdparty/protocols", undefined, undefined, + return this.http.authedRequest>( + undefined, Method.Get, "/thirdparty/protocols", undefined, undefined, ).then((response) => { // sanity check if (!response || typeof (response) !== 'object') { @@ -8263,7 +8284,7 @@ export class MatrixClient extends EventEmitter { $protocol: protocol, }); - return this.http.authedRequest(undefined, "GET", path, params, undefined); + return this.http.authedRequest(undefined, Method.Get, path, params, undefined); } /** @@ -8279,12 +8300,12 @@ export class MatrixClient extends EventEmitter { $protocol: protocol, }); - return this.http.authedRequest(undefined, "GET", path, params, undefined); + return this.http.authedRequest(undefined, Method.Get, path, params, undefined); } public getTerms(serviceType: SERVICE_TYPES, baseUrl: string): Promise { // TODO: Types const url = this.termsUrlForService(serviceType, baseUrl); - return this.http.requestOtherUrl(undefined, 'GET', url); + return this.http.requestOtherUrl(undefined, Method.Get, url); } public agreeToTerms( @@ -8297,7 +8318,7 @@ export class MatrixClient extends EventEmitter { const headers = { Authorization: "Bearer " + accessToken, }; - return this.http.requestOtherUrl(undefined, 'POST', url, null, { user_accepts: termsUrls }, { headers }); + return this.http.requestOtherUrl(undefined, Method.Post, url, null, { user_accepts: termsUrls }, { headers }); } /** @@ -8314,7 +8335,7 @@ export class MatrixClient extends EventEmitter { $eventId: eventId, }); - return this.http.authedRequest(undefined, "POST", path, null, { score, reason }); + return this.http.authedRequest(undefined, Method.Post, path, null, { score, reason }); } /** @@ -8340,7 +8361,7 @@ export class MatrixClient extends EventEmitter { $roomId: roomId, }); - return this.http.authedRequest(undefined, "POST", path, null, { + return this.http.authedRequest(undefined, Method.Post, path, null, { max_rooms_per_space: maxRoomsPerSpace, suggested_only: suggestedOnly, auto_join_only: autoJoinOnly, @@ -8367,21 +8388,32 @@ export class MatrixClient extends EventEmitter { maxDepth?: number, suggestedOnly = false, fromToken?: string, - ): Promise<{ - rooms: IHierarchyRoom[]; - next_batch?: string; // eslint-disable-line camelcase - }> { + ): Promise { const path = utils.encodeUri("/rooms/$roomId/hierarchy", { $roomId: roomId, }); - return this.http.authedRequest(undefined, "GET", path, { - suggested_only: suggestedOnly, - max_depth: maxDepth, + return this.http.authedRequest(undefined, Method.Get, path, { + suggested_only: String(suggestedOnly), + max_depth: maxDepth?.toString(), from: fromToken, - limit, + limit: limit?.toString(), }, undefined, { - prefix: "/_matrix/client/unstable/org.matrix.msc2946", + prefix: PREFIX_V1, + }).catch(e => { + if (e.errcode === "M_UNRECOGNIZED") { + // fall back to the development prefix + return this.http.authedRequest(undefined, Method.Get, path, { + suggested_only: String(suggestedOnly), + max_depth: String(maxDepth), + from: fromToken, + limit: String(limit), + }, undefined, { + prefix: "/_matrix/client/unstable/org.matrix.msc2946", + }); + } + + throw e; }).catch(e => { if (e.errcode === "M_UNRECOGNIZED") { // fall back to the older space summary API as it exposes the same data just in a different shape. @@ -8486,7 +8518,7 @@ export class MatrixClient extends EventEmitter { */ public getGroupSummary(groupId: string): Promise { const path = utils.encodeUri("/groups/$groupId/summary", { $groupId: groupId }); - return this.http.authedRequest(undefined, "GET", path); + return this.http.authedRequest(undefined, Method.Get, path); } /** @@ -8497,7 +8529,7 @@ export class MatrixClient extends EventEmitter { */ public getGroupProfile(groupId: string): Promise { const path = utils.encodeUri("/groups/$groupId/profile", { $groupId: groupId }); - return this.http.authedRequest(undefined, "GET", path); + return this.http.authedRequest(undefined, Method.Get, path); } /** @@ -8514,7 +8546,7 @@ export class MatrixClient extends EventEmitter { public setGroupProfile(groupId: string, profile: any): Promise { const path = utils.encodeUri("/groups/$groupId/profile", { $groupId: groupId }); return this.http.authedRequest( - undefined, "POST", path, undefined, profile, + undefined, Method.Post, path, undefined, profile, ); } @@ -8534,7 +8566,7 @@ export class MatrixClient extends EventEmitter { { $groupId: groupId }, ); return this.http.authedRequest( - undefined, "PUT", path, undefined, { + undefined, Method.Put, path, undefined, { 'm.join_policy': policy, }, ); @@ -8548,7 +8580,7 @@ export class MatrixClient extends EventEmitter { */ public getGroupUsers(groupId: string): Promise { const path = utils.encodeUri("/groups/$groupId/users", { $groupId: groupId }); - return this.http.authedRequest(undefined, "GET", path); + return this.http.authedRequest(undefined, Method.Get, path); } /** @@ -8559,7 +8591,7 @@ export class MatrixClient extends EventEmitter { */ public getGroupInvitedUsers(groupId: string): Promise { const path = utils.encodeUri("/groups/$groupId/invited_users", { $groupId: groupId }); - return this.http.authedRequest(undefined, "GET", path); + return this.http.authedRequest(undefined, Method.Get, path); } /** @@ -8570,7 +8602,7 @@ export class MatrixClient extends EventEmitter { */ public getGroupRooms(groupId: string): Promise { const path = utils.encodeUri("/groups/$groupId/rooms", { $groupId: groupId }); - return this.http.authedRequest(undefined, "GET", path); + return this.http.authedRequest(undefined, Method.Get, path); } /** @@ -8585,7 +8617,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/admin/users/invite/$userId", { $groupId: groupId, $userId: userId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, {}); + return this.http.authedRequest(undefined, Method.Put, path, undefined, {}); } /** @@ -8600,7 +8632,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/admin/users/remove/$userId", { $groupId: groupId, $userId: userId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, {}); + return this.http.authedRequest(undefined, Method.Put, path, undefined, {}); } /** @@ -8618,7 +8650,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/summary/users/$userId", { $groupId: groupId, $roleId: roleId, $userId: userId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, {}); + return this.http.authedRequest(undefined, Method.Put, path, undefined, {}); } /** @@ -8633,7 +8665,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/summary/users/$userId", { $groupId: groupId, $userId: userId }, ); - return this.http.authedRequest(undefined, "DELETE", path, undefined, {}); + return this.http.authedRequest(undefined, Method.Delete, path, undefined, {}); } /** @@ -8651,7 +8683,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/summary/rooms/$roomId", { $groupId: groupId, $categoryId: categoryId, $roomId: roomId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, {}); + return this.http.authedRequest(undefined, Method.Put, path, undefined, {}); } /** @@ -8666,7 +8698,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/summary/rooms/$roomId", { $groupId: groupId, $roomId: roomId }, ); - return this.http.authedRequest(undefined, "DELETE", path, undefined, {}); + return this.http.authedRequest(undefined, Method.Delete, path, undefined, {}); } /** @@ -8685,7 +8717,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/admin/rooms/$roomId", { $groupId: groupId, $roomId: roomId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, + return this.http.authedRequest(undefined, Method.Put, path, undefined, { "m.visibility": { type: isPublic ? "public" : "private" } }, ); } @@ -8708,7 +8740,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/admin/rooms/$roomId/config/m.visibility", { $groupId: groupId, $roomId: roomId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, + return this.http.authedRequest(undefined, Method.Put, path, undefined, { type: isPublic ? "public" : "private" }, ); } @@ -8725,7 +8757,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/admin/rooms/$roomId", { $groupId: groupId, $roomId: roomId }, ); - return this.http.authedRequest(undefined, "DELETE", path, undefined, {}); + return this.http.authedRequest(undefined, Method.Delete, path, undefined, {}); } /** @@ -8740,7 +8772,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/self/accept_invite", { $groupId: groupId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, opts || {}); + return this.http.authedRequest(undefined, Method.Put, path, undefined, opts || {}); } /** @@ -8754,7 +8786,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/self/join", { $groupId: groupId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, {}); + return this.http.authedRequest(undefined, Method.Put, path, undefined, {}); } /** @@ -8768,7 +8800,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/self/leave", { $groupId: groupId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, {}); + return this.http.authedRequest(undefined, Method.Put, path, undefined, {}); } /** @@ -8778,7 +8810,7 @@ export class MatrixClient extends EventEmitter { */ public getJoinedGroups(): Promise { const path = utils.encodeUri("/joined_groups", {}); - return this.http.authedRequest(undefined, "GET", path); + return this.http.authedRequest(undefined, Method.Get, path); } /** @@ -8792,7 +8824,7 @@ export class MatrixClient extends EventEmitter { public createGroup(content: any): Promise { const path = utils.encodeUri("/create_group", {}); return this.http.authedRequest( - undefined, "POST", path, undefined, content, + undefined, Method.Post, path, undefined, content, ); } @@ -8813,7 +8845,7 @@ export class MatrixClient extends EventEmitter { public getPublicisedGroups(userIds: string[]): Promise { const path = utils.encodeUri("/publicised_groups", {}); return this.http.authedRequest( - undefined, "POST", path, undefined, { user_ids: userIds }, + undefined, Method.Post, path, undefined, { user_ids: userIds }, ); } @@ -8829,7 +8861,7 @@ export class MatrixClient extends EventEmitter { "/groups/$groupId/self/update_publicity", { $groupId: groupId }, ); - return this.http.authedRequest(undefined, "PUT", path, undefined, { + return this.http.authedRequest(undefined, Method.Put, path, undefined, { publicise: isPublic, }); } @@ -8849,7 +8881,7 @@ export class MatrixClient extends EventEmitter { */ public async getRoomSummary(roomIdOrAlias: string, via?: string[]): Promise { const path = utils.encodeUri("/rooms/$roomid/summary", { $roomid: roomIdOrAlias }); - return this.http.authedRequest(undefined, "GET", path, { via }, null, { + return this.http.authedRequest(undefined, Method.Get, path, { via }, null, { qsStringifyOptions: { arrayFormat: 'repeat' }, prefix: "/_matrix/client/unstable/im.nheko.summary", }); @@ -8939,7 +8971,37 @@ export class MatrixClient extends EventEmitter { * Fetches the user_id of the configured access token. */ public async whoami(): Promise<{ user_id: string }> { // eslint-disable-line camelcase - return this.http.authedRequest(undefined, "GET", "/account/whoami"); + return this.http.authedRequest(undefined, Method.Get, "/account/whoami"); + } + + /** + * Find the event_id closest to the given timestamp in the given direction. + * @return {Promise} A promise of an object containing the event_id and + * origin_server_ts of the closest event to the timestamp in the given + * direction + */ + public async timestampToEvent( + roomId: string, + timestamp: number, + dir: Direction, + ): Promise { + const path = utils.encodeUri("/rooms/$roomId/timestamp_to_event", { + $roomId: roomId, + }); + + return await this.http.authedRequest( + undefined, + Method.Get, + path, + { + ts: timestamp.toString(), + dir: dir, + }, + undefined, + { + prefix: "/_matrix/client/unstable/org.matrix.msc3030", + }, + ); } } diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index 8eda069a4df..077d705b846 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -33,6 +33,7 @@ import { OlmDevice } from "./OlmDevice"; import { ICryptoCallbacks } from "../matrix"; import { ISignatures } from "../@types/signed"; import { CryptoStore } from "./store/base"; +import { ISecretStorageKeyInfo } from "./api"; const KEY_REQUEST_TIMEOUT_MS = 1000 * 60; @@ -175,7 +176,7 @@ export class CrossSigningInfo extends EventEmitter { // check what SSSS keys have encrypted the master key (if any) const stored = await secretStorage.isStored("m.cross_signing.master", false) || {}; // then check which of those SSSS keys have also encrypted the SSK and USK - function intersect(s) { + function intersect(s: Record) { for (const k of Object.keys(stored)) { if (!s[k]) { delete stored[k]; diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index c8a87cc566a..6e951263cab 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -28,7 +28,7 @@ import { CrossSigningInfo, ICrossSigningInfo } from './CrossSigning'; import * as olmlib from './olmlib'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; import { chunkPromises, defer, IDeferred, sleep } from '../utils'; -import { MatrixClient } from "../client"; +import { IDownloadKeyResult, MatrixClient } from "../client"; import { OlmDevice } from "./OlmDevice"; import { CryptoStore } from "./store/base"; @@ -756,17 +756,21 @@ class DeviceListUpdateSerialiser { opts.token = this.syncToken; } - const factories = []; + const factories: Array<() => Promise> = []; for (let i = 0; i < downloadUsers.length; i += this.deviceList.keyDownloadChunkSize) { const userSlice = downloadUsers.slice(i, i + this.deviceList.keyDownloadChunkSize); factories.push(() => this.baseApis.downloadKeysForUsers(userSlice, opts)); } - chunkPromises(factories, 3).then(async (responses: any[]) => { - const dk = Object.assign({}, ...(responses.map(res => res.device_keys || {}))); - const masterKeys = Object.assign({}, ...(responses.map(res => res.master_keys || {}))); - const ssks = Object.assign({}, ...(responses.map(res => res.self_signing_keys || {}))); - const usks = Object.assign({}, ...(responses.map(res => res.user_signing_keys || {}))); + chunkPromises(factories, 3).then(async (responses: IDownloadKeyResult[]) => { + const dk: IDownloadKeyResult["device_keys"] + = Object.assign({}, ...(responses.map(res => res.device_keys || {}))); + const masterKeys: IDownloadKeyResult["master_keys"] + = Object.assign({}, ...(responses.map(res => res.master_keys || {}))); + const ssks: IDownloadKeyResult["self_signing_keys"] + = Object.assign({}, ...(responses.map(res => res.self_signing_keys || {}))); + const usks: IDownloadKeyResult["user_signing_keys"] + = Object.assign({}, ...(responses.map(res => res.user_signing_keys || {}))); // yield to other things that want to execute in between users, to // avoid wedging the CPU @@ -811,8 +815,12 @@ class DeviceListUpdateSerialiser { private async processQueryResponseForUser( userId: string, - dkResponse: object, - crossSigningResponse: any, // TODO types + dkResponse: IDownloadKeyResult["device_keys"]["user_id"], + crossSigningResponse: { + master: IDownloadKeyResult["master_keys"]["user_id"]; + self_signing: IDownloadKeyResult["master_keys"]["user_id"]; // eslint-disable-line camelcase + user_signing: IDownloadKeyResult["user_signing_keys"]["user_id"]; // eslint-disable-line camelcase + }, ): Promise { logger.log('got device keys for ' + userId + ':', dkResponse); logger.log('got cross-signing keys for ' + userId + ':', crossSigningResponse); @@ -869,7 +877,7 @@ async function updateStoredDeviceKeysForUser( olmDevice: OlmDevice, userId: string, userStore: Record, - userResult: object, + userResult: IDownloadKeyResult["device_keys"]["user_id"], localUserId: string, localDeviceId: string, ): Promise { diff --git a/src/crypto/EncryptionSetup.ts b/src/crypto/EncryptionSetup.ts index 88bf7b9909d..27bcf7d780d 100644 --- a/src/crypto/EncryptionSetup.ts +++ b/src/crypto/EncryptionSetup.ts @@ -20,15 +20,9 @@ import { logger } from "../logger"; import { MatrixEvent } from "../models/event"; import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning"; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; -import { PREFIX_UNSTABLE } from "../http-api"; +import { Method, PREFIX_UNSTABLE } from "../http-api"; import { Crypto, IBootstrapCrossSigningOpts } from "./index"; -import { - CrossSigningKeys, - ICrossSigningKey, - ICryptoCallbacks, - ISignedKey, - KeySignatures, -} from "../matrix"; +import { CrossSigningKeys, ICrossSigningKey, ICryptoCallbacks, ISignedKey, KeySignatures } from "../matrix"; import { ISecretStorageKeyInfo } from "./api"; import { IKeyBackupInfo } from "./keybackup"; @@ -239,7 +233,7 @@ export class EncryptionSetupOperation { // Sign the backup with the cross signing key so the key backup can // be trusted via cross-signing. await baseApis.http.authedRequest( - undefined, "PUT", "/room_keys/version/" + this.keyBackupInfo.version, + undefined, Method.Put, "/room_keys/version/" + this.keyBackupInfo.version, undefined, { algorithm: this.keyBackupInfo.algorithm, auth_data: this.keyBackupInfo.auth_data, @@ -249,7 +243,7 @@ export class EncryptionSetupOperation { } else { // add new key backup await baseApis.http.authedRequest( - undefined, "POST", "/room_keys/version", + undefined, Method.Post, "/room_keys/version", undefined, this.keyBackupInfo, { prefix: PREFIX_UNSTABLE }, ); diff --git a/src/crypto/backup.ts b/src/crypto/backup.ts index 161e7b40fbe..5c19d166ffb 100644 --- a/src/crypto/backup.ts +++ b/src/crypto/backup.ts @@ -33,6 +33,7 @@ import { encryptAES, decryptAES, calculateKeyCheck } from './aes'; import { getCrypto } from '../utils'; import { ICurve25519AuthData, IAes256AuthData, IKeyBackupInfo, IKeyBackupSession } from "./keybackup"; import { UnstableValue } from "../NamespacedValue"; +import { IMegolmSessionData } from "./index"; const KEY_BACKUP_KEYS_PER_REQUEST = 200; @@ -87,7 +88,7 @@ interface BackupAlgorithmClass { interface BackupAlgorithm { untrusted: boolean; encryptSession(data: Record): Promise; - decryptSessions(ciphertexts: Record): Promise[]>; + decryptSessions(ciphertexts: Record): Promise; authData: AuthData; keyMatches(key: ArrayLike): Promise; free(): void; @@ -185,7 +186,6 @@ export class BackupManager { public async prepareKeyBackupVersion( key?: string | Uint8Array | null, algorithm?: string | undefined, - // eslint-disable-next-line camelcase ): Promise { const Algorithm = algorithm ? algorithmsByName[algorithm] : DefaultAlgorithm; if (!Algorithm) { @@ -300,7 +300,7 @@ export class BackupManager { const ret = { usable: false, trusted_locally: false, - sigs: [], + sigs: [] as SigInfo[], }; if ( @@ -320,7 +320,7 @@ export class BackupManager { ret.trusted_locally = true; } - const mySigs = backupInfo.auth_data.signatures[this.baseApis.getUserId()] || []; + const mySigs = backupInfo.auth_data.signatures[this.baseApis.getUserId()] || {}; for (const keyId of Object.keys(mySigs)) { const keyIdParts = keyId.split(':'); @@ -645,9 +645,7 @@ export class Curve25519 implements BackupAlgorithm { return this.publicKey.encrypt(JSON.stringify(plainText)); } - public async decryptSessions( - sessions: Record, - ): Promise[]> { + public async decryptSessions(sessions: Record): Promise { const privKey = await this.getKey(); const decryption = new global.Olm.PkDecryption(); try { @@ -658,7 +656,7 @@ export class Curve25519 implements BackupAlgorithm { throw { errcode: MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY }; } - const keys = []; + const keys: IMegolmSessionData[] = []; for (const [sessionId, sessionData] of Object.entries(sessions)) { try { @@ -777,8 +775,8 @@ export class Aes256 implements BackupAlgorithm { return await encryptAES(JSON.stringify(plainText), this.key, data.session_id); } - async decryptSessions(sessions: Record): Promise[]> { - const keys = []; + async decryptSessions(sessions: Record): Promise { + const keys: IMegolmSessionData[] = []; for (const [sessionId, sessionData] of Object.entries(sessions)) { try { diff --git a/src/crypto/dehydration.ts b/src/crypto/dehydration.ts index c9a9211fa30..49234481853 100644 --- a/src/crypto/dehydration.ts +++ b/src/crypto/dehydration.ts @@ -22,9 +22,8 @@ import { decryptAES, encryptAES } from './aes'; import { logger } from '../logger'; import { ISecretStorageKeyInfo } from "./api"; import { Crypto } from "./index"; - -// FIXME: these types should eventually go in a different file -type Signatures = Record>; +import { Method } from "../http-api"; +import { ISignatures } from "../@types/signed"; export interface IDehydratedDevice { device_id: string; // eslint-disable-line camelcase @@ -43,13 +42,13 @@ export interface IDeviceKeys { device_id: string; // eslint-disable-line camelcase user_id: string; // eslint-disable-line camelcase keys: Record; - signatures?: Signatures; + signatures?: ISignatures; } export interface IOneTimeKey { key: string; fallback?: boolean; - signatures?: Signatures; + signatures?: ISignatures; } export const DEHYDRATION_ALGORITHM = "org.matrix.msc2697.v1.olm.libolm_pickle"; @@ -207,9 +206,10 @@ export class DehydrationManager { } logger.log("Uploading account to server"); - const dehydrateResult = await this.crypto.baseApis.http.authedRequest( + // eslint-disable-next-line camelcase + const dehydrateResult = await this.crypto.baseApis.http.authedRequest<{ device_id: string }>( undefined, - "PUT", + Method.Put, "/dehydrated_device", undefined, { @@ -244,7 +244,7 @@ export class DehydrationManager { } logger.log("Preparing one-time keys"); - const oneTimeKeys = {}; + const oneTimeKeys: Record = {}; for (const [keyId, key] of Object.entries(otks.curve25519)) { const k: IOneTimeKey = { key }; const signature = account.sign(anotherjson.stringify(k)); @@ -272,7 +272,7 @@ export class DehydrationManager { logger.log("Uploading keys to server"); await this.crypto.baseApis.http.authedRequest( undefined, - "POST", + Method.Post, "/keys/upload/" + encodeURI(deviceId), undefined, { diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 6b71ed8ec93..85ba4de45c0 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -179,6 +179,13 @@ export interface IEventDecryptionResult { untrusted?: boolean; } +export interface IRequestsMap { + getRequest(event: MatrixEvent): VerificationRequest; + getRequestByChannel(channel: IVerificationChannel): VerificationRequest; + setRequest(event: MatrixEvent, request: VerificationRequest): void; + setRequestByChannel(channel: IVerificationChannel, request: VerificationRequest): void; +} + export class Crypto extends EventEmitter { /** * @return {string} The version of Olm. @@ -808,7 +815,7 @@ export class Crypto extends EventEmitter { } }; - const signKeyBackupWithCrossSigning = async (keyBackupAuthData) => { + const signKeyBackupWithCrossSigning = async (keyBackupAuthData: IKeyBackupInfo["auth_data"]) => { if ( this.crossSigningInfo.getId() && await this.crossSigningInfo.isStoredInKeyCache("master") @@ -1148,7 +1155,7 @@ export class Crypto extends EventEmitter { const signedDevice = await this.crossSigningInfo.signDevice(this.userId, device); logger.info(`Starting background key sig upload for ${this.deviceId}`); - const upload = ({ shouldEmit }) => { + const upload = ({ shouldEmit = false }) => { return this.baseApis.uploadKeySignatures({ [this.userId]: { [this.deviceId]: signedDevice, @@ -1184,7 +1191,7 @@ export class Crypto extends EventEmitter { // Check all users for signatures if upgrade callback present // FIXME: do this in batches - const users = {}; + const users: Record = {}; for (const [userId, crossSigningInfo] of Object.entries(this.deviceList.crossSigningInfo)) { const upgradeInfo = await this.checkForDeviceVerificationUpgrade( @@ -1482,7 +1489,7 @@ export class Crypto extends EventEmitter { !crossSigningPrivateKeys.has("user_signing") ); - const keySignatures = {}; + const keySignatures: Record = {}; if (selfSigningChanged) { logger.info("Got new self-signing key", newCrossSigning.getId("self_signing")); @@ -1537,7 +1544,7 @@ export class Crypto extends EventEmitter { // We may have existing signatures from deleted devices, which will cause // the entire upload to fail. keySignatures[this.crossSigningInfo.getId()] = Object.assign( - {}, + {} as ISignedKey, masterKey, { signatures: { @@ -1551,7 +1558,7 @@ export class Crypto extends EventEmitter { const keysToUpload = Object.keys(keySignatures); if (keysToUpload.length) { - const upload = ({ shouldEmit }) => { + const upload = ({ shouldEmit = false }) => { logger.info(`Starting background key sig upload for ${keysToUpload}`); return this.baseApis.uploadKeySignatures({ [this.userId]: keySignatures }) .then((response) => { @@ -1927,7 +1934,7 @@ export class Crypto extends EventEmitter { } const oneTimeKeys = await this.olmDevice.getOneTimeKeys(); - const oneTimeJson = {}; + const oneTimeJson: Record = {}; for (const keyId in oneTimeKeys.curve25519) { if (oneTimeKeys.curve25519.hasOwnProperty(keyId)) { @@ -2076,7 +2083,7 @@ export class Crypto extends EventEmitter { ); const device = await this.crossSigningInfo.signUser(xsk); if (device) { - const upload = async ({ shouldEmit }) => { + const upload = async ({ shouldEmit = false }) => { logger.info("Uploading signature for " + userId + "..."); const response = await this.baseApis.uploadKeySignatures({ [userId]: { @@ -2149,7 +2156,7 @@ export class Crypto extends EventEmitter { logger.info("Own device " + deviceId + " marked verified: signing"); // Signing only needed if other device not already signed - let device; + let device: ISignedKey; const deviceTrust = this.checkDeviceTrust(userId, deviceId); if (deviceTrust.isCrossSigningVerified()) { logger.log(`Own device ${deviceId} already cross-signing verified`); @@ -2160,7 +2167,7 @@ export class Crypto extends EventEmitter { } if (device) { - const upload = async ({ shouldEmit }) => { + const upload = async ({ shouldEmit = false }) => { logger.info("Uploading signature for " + deviceId); const response = await this.baseApis.uploadKeySignatures({ [userId]: { @@ -2204,11 +2211,7 @@ export class Crypto extends EventEmitter { return Promise.resolve(existingRequest); } const channel = new InRoomChannel(this.baseApis, roomId, userId); - return this.requestVerificationWithChannel( - userId, - channel, - this.inRoomVerificationRequests, - ); + return this.requestVerificationWithChannel(userId, channel, this.inRoomVerificationRequests); } public requestVerification(userId: string, devices: string[]): Promise { @@ -2220,17 +2223,13 @@ export class Crypto extends EventEmitter { return Promise.resolve(existingRequest); } const channel = new ToDeviceChannel(this.baseApis, userId, devices, ToDeviceChannel.makeTransactionId()); - return this.requestVerificationWithChannel( - userId, - channel, - this.toDeviceVerificationRequests, - ); + return this.requestVerificationWithChannel(userId, channel, this.toDeviceVerificationRequests); } private async requestVerificationWithChannel( userId: string, channel: IVerificationChannel, - requestsMap: any, // TODO types + requestsMap: IRequestsMap, ): Promise { let request = new VerificationRequest(channel, this.verificationMethods, this.baseApis); // if transaction id is already known, add request @@ -2618,7 +2617,7 @@ export class Crypto extends EventEmitter { users: string[], force?: boolean, ): Promise>> { - const devicesByUser = {}; + const devicesByUser: Record = {}; for (let i = 0; i < users.length; ++i) { const userId = users[i]; @@ -2651,7 +2650,7 @@ export class Crypto extends EventEmitter { * @return {module:crypto/OlmDevice.MegolmSessionData[]} a list of session export objects */ public async exportRoomKeys(): Promise { - const exportedSessions = []; + const exportedSessions: IMegolmSessionData[] = []; await this.cryptoStore.doTxn( 'readonly', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => { this.cryptoStore.getAllEndToEndInboundGroupSessions(txn, (s) => { @@ -2783,8 +2782,7 @@ export class Crypto extends EventEmitter { delete content['io.element.performance_metrics']; } - const encryptedContent = await alg.encryptMessage( - room, event.getType(), content); + const encryptedContent = await alg.encryptMessage(room, event.getType(), content); if (mRelatesTo) { encryptedContent['m.relates_to'] = mRelatesTo; @@ -3154,7 +3152,7 @@ export class Crypto extends EventEmitter { if (!ToDeviceChannel.validateEvent(event, this.baseApis)) { return; } - const createRequest = event => { + const createRequest = (event: MatrixEvent) => { if (!ToDeviceChannel.canCreateRequest(ToDeviceChannel.getEventType(event))) { return; } @@ -3172,11 +3170,7 @@ export class Crypto extends EventEmitter { return new VerificationRequest( channel, this.verificationMethods, this.baseApis); }; - this.handleVerificationEvent( - event, - this.toDeviceVerificationRequests, - createRequest, - ); + this.handleVerificationEvent(event, this.toDeviceVerificationRequests, createRequest); } /** @@ -3199,7 +3193,7 @@ export class Crypto extends EventEmitter { if (!InRoomChannel.validateEvent(event, this.baseApis)) { return; } - const createRequest = event => { + const createRequest = (event: MatrixEvent) => { const channel = new InRoomChannel( this.baseApis, event.getRoomId(), @@ -3207,18 +3201,13 @@ export class Crypto extends EventEmitter { return new VerificationRequest( channel, this.verificationMethods, this.baseApis); }; - this.handleVerificationEvent( - event, - this.inRoomVerificationRequests, - createRequest, - liveEvent, - ); + this.handleVerificationEvent(event, this.inRoomVerificationRequests, createRequest, liveEvent); }; private async handleVerificationEvent( event: MatrixEvent, - requestsMap: any, // TODO types - createRequest: any, // TODO types + requestsMap: IRequestsMap, + createRequest: (event: MatrixEvent) => VerificationRequest, isLiveEvent = true, ): Promise { // Wait for event to get its final ID with pendingEventOrdering: "chronological", since DM channels depend on it. @@ -3332,7 +3321,7 @@ export class Crypto extends EventEmitter { return; } } - const devicesByUser = {}; + const devicesByUser: Record = {}; devicesByUser[sender] = [device]; await olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser, true); diff --git a/src/crypto/olmlib.ts b/src/crypto/olmlib.ts index fe90607d915..19c34fe2b55 100644 --- a/src/crypto/olmlib.ts +++ b/src/crypto/olmlib.ts @@ -28,7 +28,8 @@ import { OlmDevice } from "./OlmDevice"; import { DeviceInfo } from "./deviceinfo"; import { logger } from '../logger'; import { IOneTimeKey } from "./dehydration"; -import { MatrixClient } from "../client"; +import { IClaimOTKsResult, MatrixClient } from "../client"; +import { ISignatures } from "../@types/signed"; enum Algorithm { Olm = "m.olm.v1.curve25519-aes-sha2", @@ -132,6 +133,11 @@ export async function encryptMessageForDevice( ); } +interface IExistingOlmSession { + device: DeviceInfo; + sessionId?: string; +} + /** * Get the existing olm sessions for the given devices, and the devices that * don't have olm sessions. @@ -152,11 +158,11 @@ export async function getExistingOlmSessions( olmDevice: OlmDevice, baseApis: MatrixClient, devicesByUser: Record, -) { - const devicesWithoutSession = {}; - const sessions = {}; +): Promise<[Record, Record>]> { + const devicesWithoutSession: {[userId: string]: DeviceInfo[]} = {}; + const sessions: {[userId: string]: {[deviceId: string]: IExistingOlmSession}} = {}; - const promises = []; + const promises: Promise[] = []; for (const [userId, devices] of Object.entries(devicesByUser)) { for (const deviceInfo of devices) { @@ -230,10 +236,10 @@ export async function ensureOlmSessionsForDevices( force = false; } - const devicesWithoutSession = [ + const devicesWithoutSession: [string, string][] = [ // [userId, deviceId], ... ]; - const result = {}; + const result: {[userId: string]: {[deviceId: string]: IExistingOlmSession}} = {}; const resolveSession: Record void> = {}; // Mark all sessions this task intends to update as in progress. It is @@ -321,9 +327,7 @@ export async function ensureOlmSessionsForDevices( let taskDetail = `one-time keys for ${devicesWithoutSession.length} devices`; try { log.debug(`Claiming ${taskDetail}`); - res = await baseApis.claimOneTimeKeys( - devicesWithoutSession, oneTimeKeyAlgorithm, otkTimeout, - ); + res = await baseApis.claimOneTimeKeys(devicesWithoutSession, oneTimeKeyAlgorithm, otkTimeout); log.debug(`Claimed ${taskDetail}`); } catch (e) { for (const resolver of Object.values(resolveSession)) { @@ -337,8 +341,8 @@ export async function ensureOlmSessionsForDevices( failedServers.push(...Object.keys(res.failures)); } - const otkResult = res.one_time_keys || {}; - const promises = []; + const otkResult = res.one_time_keys || {} as IClaimOTKsResult["one_time_keys"]; + const promises: Promise[] = []; for (const [userId, devices] of Object.entries(devicesByUser)) { const userRes = otkResult[userId] || {}; for (let j = 0; j < devices.length; j++) { @@ -359,7 +363,7 @@ export async function ensureOlmSessionsForDevices( } const deviceRes = userRes[deviceId] || {}; - let oneTimeKey = null; + let oneTimeKey: IOneTimeKey = null; for (const keyId in deviceRes) { if (keyId.indexOf(oneTimeKeyAlgorithm + ":") === 0) { oneTimeKey = deviceRes[keyId]; @@ -441,7 +445,7 @@ async function _verifyKeyAndStartSession( export interface IObject { unsigned?: object; - signatures?: object; + signatures?: ISignatures; } /** diff --git a/src/crypto/store/indexeddb-crypto-store-backend.ts b/src/crypto/store/indexeddb-crypto-store-backend.ts index 3f25a0353de..8e311a1a2b9 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.ts +++ b/src/crypto/store/indexeddb-crypto-store-backend.ts @@ -200,10 +200,10 @@ export class Backend implements CryptoStore { // index into the wantedStates array let stateIndex = 0; - let result; + let result: OutgoingRoomKeyRequest; - function onsuccess(ev) { - const cursor = ev.target.result; + function onsuccess(this: IDBRequest) { + const cursor = this.result; if (cursor) { // got a match result = cursor.value; @@ -218,7 +218,7 @@ export class Backend implements CryptoStore { } const wantedState = wantedStates[stateIndex]; - const cursorReq = ev.target.source.openCursor(wantedState); + const cursorReq = (this.source as IDBIndex).openCursor(wantedState); cursorReq.onsuccess = onsuccess; } @@ -255,10 +255,10 @@ export class Backend implements CryptoStore { wantedStates: number[], ): Promise { let stateIndex = 0; - const results = []; + const results: OutgoingRoomKeyRequest[] = []; - function onsuccess(ev) { - const cursor = ev.target.result; + function onsuccess(this: IDBRequest) { + const cursor = this.result; if (cursor) { const keyReq = cursor.value; if (keyReq.recipients.includes({ userId, deviceId })) { @@ -274,7 +274,7 @@ export class Backend implements CryptoStore { } const wantedState = wantedStates[stateIndex]; - const cursorReq = ev.target.source.openCursor(wantedState); + const cursorReq = (this.source as IDBIndex).openCursor(wantedState); cursorReq.onsuccess = onsuccess; } } @@ -306,10 +306,10 @@ export class Backend implements CryptoStore { expectedState: number, updates: Partial, ): Promise { - let result = null; + let result: OutgoingRoomKeyRequest = null; - function onsuccess(ev) { - const cursor = ev.target.result; + function onsuccess(this: IDBRequest) { + const cursor = this.result; if (!cursor) { return; } @@ -444,7 +444,7 @@ export class Backend implements CryptoStore { const objectStore = txn.objectStore("sessions"); const idx = objectStore.index("deviceKey"); const getReq = idx.openCursor(deviceKey); - const results = {}; + const results: Parameters[2]>[0] = {}; getReq.onsuccess = function() { const cursor = getReq.result; if (cursor) { @@ -734,7 +734,7 @@ export class Backend implements CryptoStore { } public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record) => void): void { - const rooms = {}; + const rooms: Parameters[1]>[0] = {}; const objectStore = txn.objectStore("rooms"); const getReq = objectStore.openCursor(); getReq.onsuccess = function() { @@ -756,7 +756,7 @@ export class Backend implements CryptoStore { public getSessionsNeedingBackup(limit: number): Promise { return new Promise((resolve, reject) => { - const sessions = []; + const sessions: ISession[] = []; const txn = this.db.transaction( ["sessions_needing_backup", "inbound_group_sessions"], @@ -877,8 +877,8 @@ export class Backend implements CryptoStore { func: (txn: IDBTransaction) => T, log: PrefixedLogger = logger, ): Promise { - let startTime; - let description; + let startTime: number; + let description: string; if (PROFILE_TRANSACTIONS) { const txnId = this.nextTxnId++; startTime = Date.now(); diff --git a/src/crypto/store/localStorage-crypto-store.ts b/src/crypto/store/localStorage-crypto-store.ts index 08f23affee3..bd0b87bc435 100644 --- a/src/crypto/store/localStorage-crypto-store.ts +++ b/src/crypto/store/localStorage-crypto-store.ts @@ -175,8 +175,10 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { } public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise { - const notifiedErrorDevices = getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {}; - const ret = []; + const notifiedErrorDevices = getJsonItem( + this.store, KEY_NOTIFIED_ERROR_DEVICES, + ) || {}; + const ret: IOlmDevice[] = []; for (const device of devices) { const { userId, deviceInfo } = device; @@ -291,7 +293,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { } public getEndToEndRooms(txn: unknown, func: (rooms: Record) => void): void { - const result = {}; + const result: Record = {}; const prefix = keyEndToEndRoomsPrefix(''); for (let i = 0; i < this.store.length; ++i) { @@ -306,7 +308,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { public getSessionsNeedingBackup(limit: number): Promise { const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; - const sessions = []; + const sessions: ISession[] = []; for (const session in sessionsNeedingBackup) { if (Object.prototype.hasOwnProperty.call(sessionsNeedingBackup, session)) { diff --git a/src/crypto/verification/request/Channel.ts b/src/crypto/verification/request/Channel.ts index 88955006b61..3bba7d82284 100644 --- a/src/crypto/verification/request/Channel.ts +++ b/src/crypto/verification/request/Channel.ts @@ -30,4 +30,5 @@ export interface IVerificationChannel { sendCompleted(type: string, content: Record): Promise; completedContentFromEvent(event: MatrixEvent): Record; canCreateRequest(type: string): boolean; + handleEvent(event: MatrixEvent, request: VerificationRequest, isLiveEvent: boolean): Promise; } diff --git a/src/crypto/verification/request/InRoomChannel.ts b/src/crypto/verification/request/InRoomChannel.ts index f07c39ffb87..9e8690e1d44 100644 --- a/src/crypto/verification/request/InRoomChannel.ts +++ b/src/crypto/verification/request/InRoomChannel.ts @@ -26,6 +26,7 @@ import { IVerificationChannel } from "./Channel"; import { EventType } from "../../../@types/event"; import { MatrixClient } from "../../../client"; import { MatrixEvent } from "../../../models/event"; +import { IRequestsMap } from "../.."; const MESSAGE_TYPE = EventType.RoomMessage; const M_REFERENCE = "m.reference"; @@ -304,7 +305,7 @@ export class InRoomChannel implements IVerificationChannel { } } -export class InRoomRequests { +export class InRoomRequests implements IRequestsMap { private requestsByRoomId = new Map>(); public getRequest(event: MatrixEvent): VerificationRequest { @@ -328,7 +329,7 @@ export class InRoomRequests { this.doSetRequest(event.getRoomId(), InRoomChannel.getTransactionId(event), request); } - public setRequestByChannel(channel: InRoomChannel, request: VerificationRequest): void { + public setRequestByChannel(channel: IVerificationChannel, request: VerificationRequest): void { this.doSetRequest(channel.roomId, channel.transactionId, request); } diff --git a/src/crypto/verification/request/ToDeviceChannel.ts b/src/crypto/verification/request/ToDeviceChannel.ts index 538a3db2c36..61bd8bc34eb 100644 --- a/src/crypto/verification/request/ToDeviceChannel.ts +++ b/src/crypto/verification/request/ToDeviceChannel.ts @@ -30,8 +30,9 @@ import { errorFromEvent, newUnexpectedMessageError } from "../Error"; import { MatrixEvent } from "../../../models/event"; import { IVerificationChannel } from "./Channel"; import { MatrixClient } from "../../../client"; +import { IRequestsMap } from '../..'; -type Request = VerificationRequest; +export type Request = VerificationRequest; /** * A key verification channel that sends verification events over to_device messages. @@ -276,7 +277,7 @@ export class ToDeviceChannel implements IVerificationChannel { private async sendToDevices(type: string, content: Record, devices: string[]): Promise { if (devices.length) { - const msgMap = {}; + const msgMap: Record> = {}; for (const deviceId of devices) { msgMap[deviceId] = content; } @@ -294,7 +295,7 @@ export class ToDeviceChannel implements IVerificationChannel { } } -export class ToDeviceRequests { +export class ToDeviceRequests implements IRequestsMap { private requestsByUserId = new Map>(); public getRequest(event: MatrixEvent): Request { diff --git a/src/crypto/verification/request/VerificationRequest.ts b/src/crypto/verification/request/VerificationRequest.ts index 5d145508be7..b6c0d9ef4bb 100644 --- a/src/crypto/verification/request/VerificationRequest.ts +++ b/src/crypto/verification/request/VerificationRequest.ts @@ -582,7 +582,9 @@ export class VerificationRequest(); this.commonMethods = content.methods.filter(m => this.verificationMethods.has(m)); } diff --git a/src/http-api.js b/src/http-api.ts similarity index 68% rename from src/http-api.js rename to src/http-api.ts index 3b73484d1f9..ac98c35ae1c 100644 --- a/src/http-api.js +++ b/src/http-api.ts @@ -1,6 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019 - 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. @@ -20,14 +20,21 @@ limitations under the License. * @module http-api */ -import { parse as parseContentType } from "content-type"; +import { parse as parseContentType, ParsedMediaType } from "content-type"; +import EventEmitter from "events"; -import * as utils from "./utils"; -import { logger } from './logger'; +import type { IncomingHttpHeaders, IncomingMessage } from "http"; +import type { Request as _Request, CoreOptions } from "request"; // we use our own implementation of setTimeout, so that if we get suspended in // the middle of a /sync, we cancel the sync as soon as we awake, rather than // waiting for the delay to elapse. import * as callbacks from "./realtime-callbacks"; +import { IUploadOpts } from "./@types/requests"; +import { IAbortablePromise } from "./@types/partials"; +import { IDeferred } from "./utils"; +import { Callback } from "./client"; +import * as utils from "./utils"; +import { logger } from './logger'; /* TODO: @@ -40,6 +47,11 @@ TODO: */ export const PREFIX_R0 = "/_matrix/client/r0"; +/** + * A constant representing the URI path for release v1 of the Client-Server HTTP API. + */ +export const PREFIX_V1 = "/_matrix/client/v1"; + /** * A constant representing the URI path for as-yet unspecified Client-Server HTTP APIs. */ @@ -61,10 +73,95 @@ export const PREFIX_IDENTITY_V2 = "/_matrix/identity/v2"; */ export const PREFIX_MEDIA_R0 = "/_matrix/media/r0"; +type RequestProps = "method" + | "withCredentials" + | "json" + | "headers" + | "qs" + | "body" + | "qsStringifyOptions" + | "useQuerystring" + | "timeout"; + +export interface IHttpOpts { + baseUrl: string; + idBaseUrl?: string; + prefix: string; + onlyData: boolean; + accessToken?: string; + extraParams?: Record; + localTimeoutMs?: number; + useAuthorizationHeader?: boolean; + request(opts: Pick & { + uri: string; + method: Method; + // eslint-disable-next-line camelcase + _matrix_opts: IHttpOpts; + }, callback: RequestCallback): IRequest; +} + +interface IRequest extends _Request { + onprogress?(e: unknown): void; +} + +interface IRequestOpts { + prefix?: string; + localTimeoutMs?: number; + headers?: Record; + json?: boolean; // defaults to true + qsStringifyOptions?: CoreOptions["qsStringifyOptions"]; + bodyParser?(body: string): T; +} + +export interface IUpload { + loaded: number; + total: number; + promise: IAbortablePromise; +} + +interface IContentUri { + base: string; + path: string; + params: { + // eslint-disable-next-line camelcase + access_token: string; + }; +} + +type ResponseType | void = void> = + O extends { bodyParser: (body: string) => T } ? T : + O extends { json: false } ? string : + T; + +interface IUploadResponse { + // eslint-disable-next-line camelcase + content_uri: string; +} + +// This type's defaults only work for the Browser +// in the Browser we default rawResponse = false & onlyContentUri = true +// in Node we default rawResponse = true & onlyContentUri = false +export type UploadContentResponseType = + O extends undefined ? string : + O extends { rawResponse: true } ? string : + O extends { onlyContentUri: true } ? string : + O extends { rawResponse: false } ? IUploadResponse : + O extends { onlyContentUri: false } ? IUploadResponse : + string; + +export enum Method { + Get = "GET", + Put = "PUT", + Post = "POST", + Delete = "DELETE", +} + +export type FileType = Document | XMLHttpRequestBodyInit; + /** * Construct a MatrixHttpApi. * @constructor - * @param {EventEmitter} event_emitter The event emitter to use for emitting events + * @param {EventEmitter} eventEmitter The event emitter to use for emitting events * @param {Object} opts The options to use for this HTTP API. * @param {string} opts.baseUrl Required. The base client-server URL e.g. * 'http://localhost:8008'. @@ -77,7 +174,7 @@ export const PREFIX_MEDIA_R0 = "/_matrix/media/r0"; * response (e.g. the parsed HTTP body). If false, requests will return an * object with the properties code, headers and data. * - * @param {string} opts.accessToken The access_token to send with requests. Can be + * @param {string=} opts.accessToken The access_token to send with requests. Can be * null to not send an access token. * @param {Object=} opts.extraParams Optional. Extra query parameters to send on * requests. @@ -86,39 +183,37 @@ export const PREFIX_MEDIA_R0 = "/_matrix/media/r0"; * @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use * Authorization header instead of query param to send the access token to the server. */ -export function MatrixHttpApi(event_emitter, opts) { - utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]); - opts.onlyData = opts.onlyData || false; - this.event_emitter = event_emitter; - this.opts = opts; - this.useAuthorizationHeader = Boolean(opts.useAuthorizationHeader); - this.uploads = []; -} +export class MatrixHttpApi { + private uploads: IUpload[] = []; + + constructor(private eventEmitter: EventEmitter, public readonly opts: IHttpOpts) { + utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]); + opts.onlyData = !!opts.onlyData; + opts.useAuthorizationHeader = !!opts.useAuthorizationHeader; + } -MatrixHttpApi.prototype = { /** - * Sets the baase URL for the identity server + * Sets the base URL for the identity server * @param {string} url The new base url */ - setIdBaseUrl: function(url) { + public setIdBaseUrl(url: string): void { this.opts.idBaseUrl = url; - }, + } /** * Get the content repository url with query parameters. * @return {Object} An object with a 'base', 'path' and 'params' for base URL, * path and query parameters respectively. */ - getContentUri: function() { - const params = { - access_token: this.opts.accessToken, - }; + public getContentUri(): IContentUri { return { base: this.opts.baseUrl, path: "/_matrix/media/r0/upload", - params: params, + params: { + access_token: this.opts.accessToken, + }, }; - }, + } /** * Upload content to the homeserver @@ -160,14 +255,17 @@ MatrixHttpApi.prototype = { * determined by this.opts.onlyData, opts.rawResponse, and * opts.onlyContentUri. Rejects with an error (usually a MatrixError). */ - uploadContent: function(file, opts) { + public uploadContent( + file: FileType, + opts?: O, + ): IAbortablePromise> { if (utils.isFunction(opts)) { - // opts used to be callback + // opts used to be callback, backwards compatibility opts = { - callback: opts, - }; - } else if (opts === undefined) { - opts = {}; + callback: opts as unknown as IUploadOpts["callback"], + } as O; + } else if (!opts) { + opts = {} as O; } // default opts.includeFilename to true (ignoring falsey values) @@ -175,8 +273,8 @@ MatrixHttpApi.prototype = { // if the file doesn't have a mime type, use a default since // the HS errors if we don't supply one. - const contentType = opts.type || file.type || 'application/octet-stream'; - const fileName = opts.name || file.name; + const contentType = opts.type || (file as File).type || 'application/octet-stream'; + const fileName = opts.name || (file as File).name; // We used to recommend setting file.stream to the thing to upload on // Node.js. As of 2019-06-11, this is still in widespread use in various @@ -185,13 +283,14 @@ MatrixHttpApi.prototype = { // the browser now define a `stream` method, which leads to trouble // here, so we also check the type of `stream`. let body = file; - if (body.stream && typeof body.stream !== "function") { + const bodyStream = (body as File | Blob).stream; // this type is wrong but for legacy reasons is good enough + if (bodyStream && typeof bodyStream !== "function") { logger.warn( "Using `file.stream` as the content to upload. Future " + "versions of the js-sdk will change this to expect `file` to " + "be the content directly.", ); - body = body.stream; + body = bodyStream; } // backwards-compatibility hacks where we used to do different things @@ -234,8 +333,8 @@ MatrixHttpApi.prototype = { // (browser-request doesn't support progress either, which is also kind // of important here) - const upload = { loaded: 0, total: 0 }; - let promise; + const upload = { loaded: 0, total: 0 } as IUpload; + let promise: IAbortablePromise>; // XMLHttpRequest doesn't parse JSON for us. request normally does, but // we're setting opts.json=false so that it doesn't JSON-encode the @@ -243,7 +342,7 @@ MatrixHttpApi.prototype = { // way, we have to JSON-parse the response ourselves. let bodyParser = null; if (!rawResponse) { - bodyParser = function(rawBody) { + bodyParser = function(rawBody: string) { let body = JSON.parse(rawBody); if (onlyContentUri) { body = body.content_uri; @@ -256,25 +355,23 @@ MatrixHttpApi.prototype = { } if (global.XMLHttpRequest) { - const defer = utils.defer(); + const defer = utils.defer>(); const xhr = new global.XMLHttpRequest(); - upload.xhr = xhr; const cb = requestCallback(defer, opts.callback, this.opts.onlyData); - const timeout_fn = function() { + const timeoutFn = function() { xhr.abort(); cb(new Error('Timeout')); }; - // set an initial timeout of 30s; we'll advance it each time we get - // a progress notification - xhr.timeout_timer = callbacks.setTimeout(timeout_fn, 30000); + // set an initial timeout of 30s; we'll advance it each time we get a progress notification + let timeoutTimer = callbacks.setTimeout(timeoutFn, 30000); xhr.onreadystatechange = function() { - let resp; + let resp: string; switch (xhr.readyState) { case global.XMLHttpRequest.DONE: - callbacks.clearTimeout(xhr.timeout_timer); + callbacks.clearTimeout(timeoutTimer); try { if (xhr.status === 0) { throw new AbortError(); @@ -296,10 +393,10 @@ MatrixHttpApi.prototype = { } }; xhr.upload.addEventListener("progress", function(ev) { - callbacks.clearTimeout(xhr.timeout_timer); + callbacks.clearTimeout(timeoutTimer); upload.loaded = ev.loaded; upload.total = ev.total; - xhr.timeout_timer = callbacks.setTimeout(timeout_fn, 30000); + timeoutTimer = callbacks.setTimeout(timeoutFn, 30000); if (opts.progressHandler) { opts.progressHandler({ loaded: ev.loaded, @@ -315,9 +412,8 @@ MatrixHttpApi.prototype = { queryArgs.push("filename=" + encodeURIComponent(fileName)); } - if (!this.useAuthorizationHeader) { - queryArgs.push("access_token=" - + encodeURIComponent(this.opts.accessToken)); + if (!this.opts.useAuthorizationHeader) { + queryArgs.push("access_token=" + encodeURIComponent(this.opts.accessToken)); } if (queryArgs.length > 0) { @@ -325,73 +421,69 @@ MatrixHttpApi.prototype = { } xhr.open("POST", url); - if (this.useAuthorizationHeader) { + if (this.opts.useAuthorizationHeader) { xhr.setRequestHeader("Authorization", "Bearer " + this.opts.accessToken); } xhr.setRequestHeader("Content-Type", contentType); xhr.send(body); - promise = defer.promise; + promise = defer.promise as IAbortablePromise>; - // dirty hack (as per _request) to allow the upload to be cancelled. + // dirty hack (as per doRequest) to allow the upload to be cancelled. promise.abort = xhr.abort.bind(xhr); } else { - const queryParams = {}; + const queryParams: Record = {}; if (includeFilename && fileName) { queryParams.filename = fileName; } promise = this.authedRequest( - opts.callback, "POST", "/upload", queryParams, body, { + opts.callback, Method.Post, "/upload", queryParams, body, { prefix: "/_matrix/media/r0", headers: { "Content-Type": contentType }, json: false, - bodyParser: bodyParser, + bodyParser, }, ); } - const self = this; - // remove the upload from the list on completion - const promise0 = promise.finally(function() { - for (let i = 0; i < self.uploads.length; ++i) { - if (self.uploads[i] === upload) { - self.uploads.splice(i, 1); + upload.promise = promise.finally(() => { + for (let i = 0; i < this.uploads.length; ++i) { + if (this.uploads[i] === upload) { + this.uploads.splice(i, 1); return; } } - }); + }) as IAbortablePromise>; // copy our dirty abort() method to the new promise - promise0.abort = promise.abort; - - upload.promise = promise0; + upload.promise.abort = promise.abort; this.uploads.push(upload); - return promise0; - }, + return upload.promise as IAbortablePromise>; + } - cancelUpload: function(promise) { + public cancelUpload(promise: IAbortablePromise): boolean { if (promise.abort) { promise.abort(); return true; } return false; - }, + } - getCurrentUploads: function() { + public getCurrentUploads(): IUpload[] { return this.uploads; - }, - - idServerRequest: function( - callback, - method, - path, - params, - prefix, - accessToken, - ) { + } + + public idServerRequest( + callback: Callback, + method: Method, + path: string, + params: Record, + prefix: string, + accessToken: string, + ): Promise { if (!this.opts.idBaseUrl) { throw new Error("No identity server base URL set"); } @@ -406,28 +498,27 @@ MatrixHttpApi.prototype = { const opts = { uri: fullUri, - method: method, + method, withCredentials: false, json: true, // we want a JSON response if we can _matrix_opts: this.opts, headers: {}, - }; - if (method === 'GET') { + } as Parameters[0]; + + if (method === Method.Get) { opts.qs = params; } else if (typeof params === "object") { - opts.json = params; + opts.json = !!params; // XXX: this feels strange } + if (accessToken) { opts.headers['Authorization'] = `Bearer ${accessToken}`; } - const defer = utils.defer(); - this.opts.request( - opts, - requestCallback(defer, callback, this.opts.onlyData), - ); + const defer = utils.defer(); + this.opts.request(opts, requestCallback(defer, callback, this.opts.onlyData)); return defer.promise; - }, + } /** * Perform an authorised request to the homeserver. @@ -448,7 +539,7 @@ MatrixHttpApi.prototype = { * @param {Number=} opts.localTimeoutMs The maximum amount of time to wait before * timing out the request. If not specified, there is no timeout. * - * @param {sting=} opts.prefix The full prefix to use e.g. + * @param {string=} opts.prefix The full prefix to use e.g. * "/_matrix/client/v2_alpha". If not specified, uses this.opts.prefix. * * @param {Object=} opts.headers map of additional request headers @@ -460,45 +551,45 @@ MatrixHttpApi.prototype = { * @return {module:http-api.MatrixError} Rejects with an error if a problem * occurred. This includes network problems and Matrix-specific error JSON. */ - authedRequest: function(callback, method, path, queryParams, data, opts) { - if (!queryParams) { - queryParams = {}; - } - if (this.useAuthorizationHeader) { - if (isFinite(opts)) { + public authedRequest = IRequestOpts>( + callback: Callback, + method: Method, + path: string, + queryParams?: Record, + data?: CoreOptions["body"], + opts?: O | number, // number is legacy + ): IAbortablePromise> { + if (!queryParams) queryParams = {}; + let requestOpts = (opts || {}) as O; + + if (this.opts.useAuthorizationHeader) { + if (isFinite(opts as number)) { // opts used to be localTimeoutMs - opts = { - localTimeoutMs: opts, - }; - } - if (!opts) { - opts = {}; + requestOpts = { + localTimeoutMs: opts as number, + } as O; } - if (!opts.headers) { - opts.headers = {}; + + if (!requestOpts.headers) { + requestOpts.headers = {}; } - if (!opts.headers.Authorization) { - opts.headers.Authorization = "Bearer " + this.opts.accessToken; + if (!requestOpts.headers.Authorization) { + requestOpts.headers.Authorization = "Bearer " + this.opts.accessToken; } if (queryParams.access_token) { delete queryParams.access_token; } - } else { - if (!queryParams.access_token) { - queryParams.access_token = this.opts.accessToken; - } + } else if (!queryParams.access_token) { + queryParams.access_token = this.opts.accessToken; } - const requestPromise = this.request( - callback, method, path, queryParams, data, opts, - ); + const requestPromise = this.request(callback, method, path, queryParams, data, requestOpts); - const self = this; - requestPromise.catch(function(err) { + requestPromise.catch((err: MatrixError) => { if (err.errcode == 'M_UNKNOWN_TOKEN') { - self.event_emitter.emit("Session.logged_out", err); + this.eventEmitter.emit("Session.logged_out", err); } else if (err.errcode == 'M_CONSENT_NOT_GIVEN') { - self.event_emitter.emit( + this.eventEmitter.emit( "no_consent", err.message, err.data.consent_uri, @@ -509,7 +600,7 @@ MatrixHttpApi.prototype = { // return the original promise, otherwise tests break due to it having to // go around the event loop one more time to process the result of the request return requestPromise; - }, + } /** * Perform a request to the homeserver without any credentials. @@ -529,7 +620,7 @@ MatrixHttpApi.prototype = { * @param {Number=} opts.localTimeoutMs The maximum amount of time to wait before * timing out the request. If not specified, there is no timeout. * - * @param {sting=} opts.prefix The full prefix to use e.g. + * @param {string=} opts.prefix The full prefix to use e.g. * "/_matrix/client/v2_alpha". If not specified, uses this.opts.prefix. * * @param {Object=} opts.headers map of additional request headers @@ -541,15 +632,19 @@ MatrixHttpApi.prototype = { * @return {module:http-api.MatrixError} Rejects with an error if a problem * occurred. This includes network problems and Matrix-specific error JSON. */ - request: function(callback, method, path, queryParams, data, opts) { - opts = opts || {}; - const prefix = opts.prefix !== undefined ? opts.prefix : this.opts.prefix; + public request = IRequestOpts>( + callback: Callback, + method: Method, + path: string, + queryParams?: CoreOptions["qs"], + data?: CoreOptions["body"], + opts?: O, + ): IAbortablePromise> { + const prefix = opts?.prefix ?? this.opts.prefix; const fullUri = this.opts.baseUrl + prefix + path; - return this.requestOtherUrl( - callback, method, fullUri, queryParams, data, opts, - ); - }, + return this.requestOtherUrl(callback, method, fullUri, queryParams, data, opts); + } /** * Perform a request to an arbitrary URL. @@ -568,7 +663,7 @@ MatrixHttpApi.prototype = { * @param {Number=} opts.localTimeoutMs The maximum amount of time to wait before * timing out the request. If not specified, there is no timeout. * - * @param {sting=} opts.prefix The full prefix to use e.g. + * @param {string=} opts.prefix The full prefix to use e.g. * "/_matrix/client/v2_alpha". If not specified, uses this.opts.prefix. * * @param {Object=} opts.headers map of additional request headers @@ -580,21 +675,24 @@ MatrixHttpApi.prototype = { * @return {module:http-api.MatrixError} Rejects with an error if a problem * occurred. This includes network problems and Matrix-specific error JSON. */ - requestOtherUrl: function(callback, method, uri, queryParams, data, - opts) { - if (opts === undefined || opts === null) { - opts = {}; - } else if (isFinite(opts)) { + public requestOtherUrl = IRequestOpts>( + callback: Callback, + method: Method, + uri: string, + queryParams?: CoreOptions["qs"], + data?: CoreOptions["body"], + opts?: O | number, // number is legacy + ): IAbortablePromise> { + let requestOpts = (opts || {}) as O; + if (isFinite(opts as number)) { // opts used to be localTimeoutMs - opts = { - localTimeoutMs: opts, - }; + requestOpts = { + localTimeoutMs: opts as number, + } as O; } - return this._request( - callback, method, uri, queryParams, data, opts, - ); - }, + return this.doRequest(callback, method, uri, queryParams, data, requestOpts); + } /** * Form and return a homeserver request URL based on the given path @@ -607,13 +705,13 @@ MatrixHttpApi.prototype = { * "/_matrix/client/v2_alpha". * @return {string} URL */ - getUrl: function(path, queryParams, prefix) { + public getUrl(path: string, queryParams: CoreOptions["qs"], prefix: string): string { let queryString = ""; if (queryParams) { queryString = "?" + utils.encodeParams(queryParams); } return this.opts.baseUrl + prefix + path + queryString; - }, + } /** * @private @@ -640,25 +738,32 @@ MatrixHttpApi.prototype = { * @return {Promise} a promise which resolves to either the * response object (if this.opts.onlyData is truthy), or the parsed * body. Rejects + * + * Generic T is the callback/promise resolve type + * Generic O should be inferred */ - _request: function(callback, method, uri, queryParams, data, opts) { + private doRequest = IRequestOpts>( + callback: Callback, + method: Method, + uri: string, + queryParams?: Record, + data?: CoreOptions["body"], + opts?: O, + ): IAbortablePromise> { if (callback !== undefined && !utils.isFunction(callback)) { - throw Error( - "Expected callback to be a function but got " + typeof callback, - ); + throw Error("Expected callback to be a function but got " + typeof callback); } - opts = opts || {}; - const self = this; if (this.opts.extraParams) { queryParams = { - ...queryParams, - ...this.opts.extraParams, + ...(queryParams || {}), + ...this.opts.extraParams, }; } const headers = Object.assign({}, opts.headers || {}); - const json = opts.json === undefined ? true : opts.json; + if (!opts) opts = {} as O; + const json = opts.json ?? true; let bodyParser = opts.bodyParser; // we handle the json encoding/decoding here, because request and @@ -677,17 +782,17 @@ MatrixHttpApi.prototype = { } if (bodyParser === undefined) { - bodyParser = function(rawBody) { + bodyParser = function(rawBody: string) { return JSON.parse(rawBody); }; } } - const defer = utils.defer(); + const defer = utils.defer(); - let timeoutId; + let timeoutId: number; let timedOut = false; - let req; + let req: IRequest; const localTimeoutMs = opts.localTimeoutMs || this.opts.localTimeoutMs; const resetTimeout = () => { @@ -697,9 +802,7 @@ MatrixHttpApi.prototype = { } timeoutId = callbacks.setTimeout(function() { timedOut = true; - if (req && req.abort) { - req.abort(); - } + req?.abort?.(); defer.reject(new MatrixError({ error: "Locally timed out waiting for a response", errcode: "ORG.MATRIX.JSSDK_TIMEOUT", @@ -710,7 +813,7 @@ MatrixHttpApi.prototype = { }; resetTimeout(); - const reqPromise = defer.promise; + const reqPromise = defer.promise as IAbortablePromise>; try { req = this.opts.request( @@ -727,7 +830,7 @@ MatrixHttpApi.prototype = { headers: headers || {}, _matrix_opts: this.opts, }, - function(err, response, body) { + (err, response, body) => { if (localTimeoutMs) { callbacks.clearTimeout(timeoutId); if (timedOut) { @@ -735,16 +838,13 @@ MatrixHttpApi.prototype = { } } - const handlerFn = requestCallback( - defer, callback, self.opts.onlyData, - bodyParser, - ); + const handlerFn = requestCallback(defer, callback, this.opts.onlyData, bodyParser); handlerFn(err, response, body); }, ); if (req) { // This will only work in a browser, where opts.request is the - // `browser-request` import. Currently `request` does not support progress + // `browser-request` import. Currently, `request` does not support progress // updates - see https://github.com/request/request/pull/2346. // `browser-request` returns an XHRHttpRequest which exposes `onprogress` if ('onprogress' in req) { @@ -757,7 +857,9 @@ MatrixHttpApi.prototype = { // FIXME: This is EVIL, but I can't think of a better way to expose // abort() operations on underlying HTTP requests :( - if (req.abort) reqPromise.abort = req.abort.bind(req); + if (req.abort) { + reqPromise.abort = req.abort.bind(req); + } } } catch (ex) { defer.reject(ex); @@ -766,8 +868,21 @@ MatrixHttpApi.prototype = { } } return reqPromise; - }, -}; + } +} + +type RequestCallback = (err?: Error, response?: XMLHttpRequest | IncomingMessage, body?: string) => void; + +// if using onlyData=false then wrap your expected data type in this generic +export interface IResponse { + code: number; + data: T; + headers?: IncomingHttpHeaders; +} + +function getStatusCode(response: XMLHttpRequest | IncomingMessage): number { + return (response as XMLHttpRequest).status || (response as IncomingMessage).statusCode; +} /* * Returns a callback that can be invoked by an HTTP request on completion, @@ -783,17 +898,17 @@ MatrixHttpApi.prototype = { * response, otherwise the result object (with `code` and `data` fields) * */ -const requestCallback = function( - defer, userDefinedCallback, onlyData, - bodyParser, -) { - userDefinedCallback = userDefinedCallback || function() {}; - - return function(err, response, body) { +function requestCallback( + defer: IDeferred, + userDefinedCallback?: Callback, + onlyData = false, + bodyParser?: (body: string) => T, +): RequestCallback { + return function(err: Error, response: XMLHttpRequest | IncomingMessage, body: string): void { if (err) { // the unit tests use matrix-mock-request, which throw the string "aborted" when aborting a request. // See https://github.com/matrix-org/matrix-mock-request/blob/3276d0263a561b5b8326b47bae720578a2c7473a/src/index.js#L48 - const aborted = err.name === "AbortError" || err === "aborted"; + const aborted = err.name === "AbortError" || (err as any as string) === "aborted"; if (!aborted && !(err instanceof MatrixError)) { // browser-request just throws normal Error objects, // not `TypeError`s like fetch does. So just assume any @@ -801,13 +916,15 @@ const requestCallback = function( err = new ConnectionError("request failed", err); } } + + let data: T | string = body; + if (!err) { try { - const httpStatus = response.status || response.statusCode; // XMLHttpRequest vs http.IncomingMessage - if (httpStatus >= 400) { + if (getStatusCode(response) >= 400) { err = parseErrorResponse(response, body); } else if (bodyParser) { - body = bodyParser(body); + data = bodyParser(body); } } catch (e) { err = new Error(`Error parsing server response: ${e}`); @@ -816,21 +933,26 @@ const requestCallback = function( if (err) { defer.reject(err); - userDefinedCallback(err); + userDefinedCallback?.(err); + } else if (onlyData) { + defer.resolve(data as T); + userDefinedCallback?.(null, data as T); } else { - const res = { - code: response.status || response.statusCode, // XMLHttpRequest vs http.IncomingMessage + const res: IResponse = { + code: getStatusCode(response), // XXX: why do we bother with this? it doesn't work for // XMLHttpRequest, so clearly we don't use it. - headers: response.headers, - data: body, + headers: (response as IncomingMessage).headers, + data: data as T, }; - defer.resolve(onlyData ? body : res); - userDefinedCallback(null, onlyData ? body : res); + // XXX: the variations in caller-expected types here are horrible, + // typescript doesn't do conditional types based on runtime values + defer.resolve(res as any as T); + userDefinedCallback?.(null, res as any as T); } }; -}; +} /** * Attempt to turn an HTTP error response into a Javascript Error. @@ -842,8 +964,8 @@ const requestCallback = function( * @param {String} body raw body of the response * @returns {Error} */ -function parseErrorResponse(response, body) { - const httpStatus = response.status || response.statusCode; // XMLHttpRequest vs http.IncomingMessage +function parseErrorResponse(response: XMLHttpRequest | IncomingMessage, body?: string) { + const httpStatus = getStatusCode(response); const contentType = getResponseContentType(response); let err; @@ -872,14 +994,14 @@ function parseErrorResponse(response, body) { * @param {XMLHttpRequest|http.IncomingMessage} response response object * @returns {{type: String, parameters: Object}?} parsed content-type header, or null if not found */ -function getResponseContentType(response) { +function getResponseContentType(response: XMLHttpRequest | IncomingMessage): ParsedMediaType { let contentType; - if (response.getResponseHeader) { + if ((response as XMLHttpRequest).getResponseHeader) { // XMLHttpRequest provides getResponseHeader - contentType = response.getResponseHeader("Content-Type"); - } else if (response.headers) { + contentType = (response as XMLHttpRequest).getResponseHeader("Content-Type"); + } else if ((response as IncomingMessage).headers) { // request provides http.IncomingMessage which has a message.headers map - contentType = response.headers['content-type'] || null; + contentType = (response as IncomingMessage).headers['content-type'] || null; } if (!contentType) { @@ -893,6 +1015,12 @@ function getResponseContentType(response) { } } +interface IErrorJson { + [key: string]: any; // extensible + errcode?: string; + error?: string; +} + /** * Construct a Matrix error. This is a JavaScript Error with additional * information specific to the standard Matrix error response. @@ -905,8 +1033,11 @@ function getResponseContentType(response) { * @prop {integer} httpStatus The numeric HTTP status code given */ export class MatrixError extends Error { - constructor(errorJson) { - errorJson = errorJson || {}; + public readonly errcode: string; + public readonly data: IErrorJson; + public httpStatus?: number; // set by http-api + + constructor(errorJson: IErrorJson = {}) { super(`MatrixError: ${errorJson.errcode}`); this.errcode = errorJson.errcode; this.name = errorJson.errcode || "Unknown error code"; @@ -923,18 +1054,13 @@ export class MatrixError extends Error { * @constructor */ export class ConnectionError extends Error { - constructor(message, cause = undefined) { + constructor(message: string, private readonly cause: Error = undefined) { super(message + (cause ? `: ${cause.message}` : "")); - this._cause = cause; } get name() { return "ConnectionError"; } - - get cause() { - return this._cause; - } } export class AbortError extends Error { @@ -954,7 +1080,7 @@ export class AbortError extends Error { * @return {any} the result of the network operation * @throws {ConnectionError} If after maxAttempts the callback still throws ConnectionError */ -export async function retryNetworkOperation(maxAttempts, callback) { +export async function retryNetworkOperation(maxAttempts: number, callback: () => T): Promise { let attempts = 0; let lastConnectionError = null; while (attempts < maxAttempts) { diff --git a/src/interactive-auth.ts b/src/interactive-auth.ts index eb00c75db80..e55aa1d2ccb 100644 --- a/src/interactive-auth.ts +++ b/src/interactive-auth.ts @@ -21,7 +21,6 @@ limitations under the License. import { logger } from './logger'; import { MatrixClient } from "./client"; import { defer, IDeferred } from "./utils"; -import { MatrixError } from "./http-api"; const EMAIL_STAGE_TYPE = "m.login.email.identity"; const MSISDN_STAGE_TYPE = "m.login.msisdn"; @@ -49,7 +48,7 @@ export interface IAuthData { flows?: IFlow[]; params?: Record>; errcode?: string; - error?: MatrixError; + error?: string; } export enum AuthType { diff --git a/src/models/MSC3089Branch.ts b/src/models/MSC3089Branch.ts index 46dd52b9a4a..d074d87c7b0 100644 --- a/src/models/MSC3089Branch.ts +++ b/src/models/MSC3089Branch.ts @@ -18,7 +18,7 @@ import { MatrixClient } from "../client"; import { IEncryptedFile, RelationType, UNSTABLE_MSC3089_BRANCH } from "../@types/event"; import { IContent, MatrixEvent } from "./event"; import { MSC3089TreeSpace } from "./MSC3089TreeSpace"; -import type { ReadStream } from "fs"; +import { FileType } from "../http-api"; /** * Represents a [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) branch - a reference @@ -160,7 +160,7 @@ export class MSC3089Branch { */ public async createNewVersion( name: string, - encryptedContents: File | String | Buffer | ReadStream | Blob, + encryptedContents: FileType, info: Partial, additionalContent?: IContent, ): Promise { diff --git a/src/models/MSC3089TreeSpace.ts b/src/models/MSC3089TreeSpace.ts index 48209dbc004..0426d596e2e 100644 --- a/src/models/MSC3089TreeSpace.ts +++ b/src/models/MSC3089TreeSpace.ts @@ -32,7 +32,7 @@ import { import { MSC3089Branch } from "./MSC3089Branch"; import { isRoomSharedHistory } from "../crypto/algorithms/megolm"; import { ISendEventResponse } from "../@types/requests"; -import type { ReadStream } from "fs"; +import { FileType } from "../http-api"; /** * The recommended defaults for a tree space's power levels. Note that this @@ -282,7 +282,7 @@ export class MSC3089TreeSpace { const members = this.room.currentState.getStateEvents(EventType.RoomMember); for (const member of members) { const isNotUs = member.getStateKey() !== this.client.getUserId(); - if (isNotUs && kickMemberships.includes(member.getContent()['membership'])) { + if (isNotUs && kickMemberships.includes(member.getContent().membership)) { const stateKey = member.getStateKey(); if (!stateKey) { throw new Error("State key not found for branch"); @@ -472,7 +472,7 @@ export class MSC3089TreeSpace { */ public async createFile( name: string, - encryptedContents: File | String | Buffer | ReadStream | Blob, + encryptedContents: FileType, info: Partial, additionalContent?: IContent, ): Promise { diff --git a/src/models/room.ts b/src/models/room.ts index 479ee46edd9..8266c42713a 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -36,6 +36,7 @@ import { GuestAccess, HistoryVisibility, JoinRule, ResizeMethod } from "../@type import { Filter } from "../filter"; import { RoomState } from "./room-state"; import { Thread } from "./thread"; +import { Method } from "../http-api"; // These constants are used as sane defaults when the homeserver doesn't support // the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be @@ -135,15 +136,49 @@ export class Room extends EventEmitter { private membersPromise?: Promise; // XXX: These should be read-only + /** + * The human-readable display name for this room. + */ public name: string; + /** + * The un-homoglyphed name for this room. + */ public normalizedName: string; + /** + * Dict of room tags; the keys are the tag name and the values + * are any metadata associated with the tag - e.g. { "fav" : { order: 1 } } + */ public tags: Record> = {}; // $tagName: { $metadata: $value } + /** + * accountData Dict of per-room account_data events; the keys are the + * event type and the values are the events. + */ public accountData: Record = {}; // $eventType: $event + /** + * The room summary. + */ public summary: RoomSummary = null; + /** + * A token which a data store can use to remember the state of the room. + */ public readonly storageToken?: string; // legacy fields + /** + * The live event timeline for this room, with the oldest event at index 0. + * Present for backwards compatibility - prefer getLiveTimeline().getEvents() + */ public timeline: MatrixEvent[]; + /** + * oldState The state of the room at the time of the oldest + * event in the live timeline. Present for backwards compatibility - + * prefer getLiveTimeline().getState(EventTimeline.BACKWARDS). + */ public oldState: RoomState; + /** + * currentState The state of the room at the time of the + * newest event in the timeline. Present for backwards compatibility - + * prefer getLiveTimeline().getState(EventTimeline.FORWARDS). + */ public currentState: RoomState; /** @@ -191,26 +226,6 @@ export class Room extends EventEmitter { * Optional. Set to true to enable client-side aggregation of event relations * via `EventTimelineSet#getRelationsForEvent`. * This feature is currently unstable and the API may change without notice. - * - * @prop {string} roomId The ID of this room. - * @prop {string} name The human-readable display name for this room. - * @prop {string} normalizedName The un-homoglyphed name for this room. - * @prop {Array} timeline The live event timeline for this room, - * with the oldest event at index 0. Present for backwards compatibility - - * prefer getLiveTimeline().getEvents(). - * @prop {object} tags Dict of room tags; the keys are the tag name and the values - * are any metadata associated with the tag - e.g. { "fav" : { order: 1 } } - * @prop {object} accountData Dict of per-room account_data events; the keys are the - * event type and the values are the events. - * @prop {RoomState} oldState The state of the room at the time of the oldest - * event in the live timeline. Present for backwards compatibility - - * prefer getLiveTimeline().getState(EventTimeline.BACKWARDS). - * @prop {RoomState} currentState The state of the room at the time of the - * newest event in the timeline. Present for backwards compatibility - - * prefer getLiveTimeline().getState(EventTimeline.FORWARDS). - * @prop {RoomSummary} summary The room summary. - * @prop {*} storageToken A token which a data store can use to remember - * the state of the room. */ constructor( public readonly roomId: string, @@ -660,7 +675,7 @@ export class Room extends EventEmitter { const path = utils.encodeUri("/rooms/$roomId/members?" + queryString, { $roomId: this.roomId }); const http = this.client.http; - const response = await http.authedRequest(undefined, "GET", path); + const response = await http.authedRequest<{ chunk: IEvent[] }>(undefined, Method.Get, path); return response.chunk; } diff --git a/src/room-hierarchy.ts b/src/room-hierarchy.ts index 16b6014ea60..1acf10e586d 100644 --- a/src/room-hierarchy.ts +++ b/src/room-hierarchy.ts @@ -112,7 +112,7 @@ export class RoomHierarchy { if (!this.backRefs.has(childRoomId)) { this.backRefs.set(childRoomId, []); } - this.backRefs.get(childRoomId).push(ev.room_id); + this.backRefs.get(childRoomId).push(room.room_id); // fill viaMap if (Array.isArray(ev.content.via)) { diff --git a/src/scheduler.ts b/src/scheduler.ts index d0436bd8312..d0249b6cc02 100644 --- a/src/scheduler.ts +++ b/src/scheduler.ts @@ -70,7 +70,7 @@ export class MatrixScheduler { } // we ship with browser-request which returns { cors: rejected } when trying // with no connection, so if we match that, give up since they have no conn. - if (err.cors === "rejected") { + if (err["cors"] === "rejected") { return -1; } diff --git a/src/store/session/webstorage.js b/src/store/session/webstorage.js index 7dcd1339db4..91d5f31e5dd 100644 --- a/src/store/session/webstorage.js +++ b/src/store/session/webstorage.js @@ -256,8 +256,8 @@ function removeByPrefix(store, prefix) { } } -function debuglog() { +function debuglog(...args) { if (DEBUG) { - logger.log(...arguments); + logger.log(...args); } } diff --git a/src/sync.ts b/src/sync.ts index a25990a5ba5..866f47abfef 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -37,20 +37,20 @@ import { IProcessEventsOpts, IStoredClientOpts, MatrixClient, PendingEventOrderi import { SyncState } from "./sync.api"; import { Category, + IEphemeral, IInvitedRoom, IInviteState, IJoinedRoom, ILeftRoom, - IStateEvent, + IMinimalEvent, IRoomEvent, + IStateEvent, IStrippedState, ISyncResponse, ITimeline, - IEphemeral, - IMinimalEvent, } from "./sync-accumulator"; import { MatrixEvent } from "./models/event"; -import { MatrixError } from "./http-api"; +import { MatrixError, Method } from "./http-api"; import { ISavedSync } from "./store"; import { EventType } from "./@types/event"; import { IPushRules } from "./@types/PushRules"; @@ -94,6 +94,12 @@ export interface ISyncStateData { fromCache?: boolean; } +enum SetPresence { + Offline = "offline", + Online = "online", + Unavailable = "unavailable", +} + interface ISyncParams { filter?: string; timeout: number; @@ -101,7 +107,7 @@ interface ISyncParams { // eslint-disable-next-line camelcase full_state?: boolean; // eslint-disable-next-line camelcase - set_presence?: "offline" | "online" | "unavailable"; + set_presence?: SetPresence; _cacheBuster?: string | number; // not part of the API itself } @@ -260,12 +266,12 @@ export class SyncApi { getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter, ).then(function(filterId) { qps.filter = filterId; - return client.http.authedRequest( - undefined, "GET", "/sync", qps, undefined, localTimeoutMs, + return client.http.authedRequest( // TODO types + undefined, Method.Get, "/sync", qps as any, undefined, localTimeoutMs, ); }).then((data) => { let leaveRooms = []; - if (data.rooms && data.rooms.leave) { + if (data.rooms?.leave) { leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave); } const rooms = []; @@ -402,9 +408,10 @@ export class SyncApi { } // FIXME: gut wrenching; hard-coded timeout values - this.client.http.authedRequest(undefined, "GET", "/events", { + // TODO types + this.client.http.authedRequest(undefined, Method.Get, "/events", { room_id: peekRoom.roomId, - timeout: 30 * 1000, + timeout: String(30 * 1000), from: token, }, undefined, 50 * 1000).then((res) => { if (this._peekRoom !== peekRoom) { @@ -867,8 +874,8 @@ export class SyncApi { private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): IRequestPromise { const qps = this.getSyncParams(syncOptions, syncToken); - return this.client.http.authedRequest( - undefined, "GET", "/sync", qps, undefined, + return this.client.http.authedRequest( // TODO types + undefined, Method.Get, "/sync", qps as any, undefined, qps.timeout + BUFFER_PERIOD_MS, ); } @@ -903,7 +910,7 @@ export class SyncApi { }; if (this.opts.disablePresence) { - qps.set_presence = "offline"; + qps.set_presence = SetPresence.Offline; } if (syncToken) { @@ -926,7 +933,7 @@ export class SyncApi { return qps; } - private onSyncError(err: Error, syncOptions: ISyncOptions): void { + private onSyncError(err: MatrixError, syncOptions: ISyncOptions): void { if (!this.running) { debuglog("Sync no longer running: exiting"); if (this.connectionReturnedDefer) { @@ -1483,7 +1490,7 @@ export class SyncApi { this.client.http.request( undefined, // callback - "GET", "/_matrix/client/versions", + Method.Get, "/_matrix/client/versions", undefined, // queryParams undefined, // data { diff --git a/src/utils.ts b/src/utils.ts index a4862b1ced6..136d7ffe013 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -27,12 +27,19 @@ import type NodeCrypto from "crypto"; /** * Encode a dictionary of query parameters. + * Omits any undefined/null values. * @param {Object} params A dict of key/values to encode e.g. * {"foo": "bar", "baz": "taz"} * @return {string} The encoded string e.g. foo=bar&baz=taz */ -export function encodeParams(params: Record): string { - return new URLSearchParams(params).toString(); +export function encodeParams(params: Record): string { + const searchParams = new URLSearchParams(); + for (const [key, val] of Object.entries(params)) { + if (val !== undefined && val !== null) { + searchParams.set(key, String(val)); + } + } + return searchParams.toString(); } export type QueryDict = Record; diff --git a/yarn.lock b/yarn.lock index 2e6b66bcbbf..d5b92f9542a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1210,9 +1210,9 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.7.tgz": - version "3.2.7" - resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.7.tgz#d54279ec26cd46518ea06af306bd450aeab207bf" +"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz": + version "3.2.8" + resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz#8d53636d045e1776e2a2ec6613e57330dd9ce856" "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" @@ -1424,6 +1424,11 @@ resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== +"@types/content-type@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.5.tgz#aa02dca40864749a9e2bf0161a6216da57e3ede5" + integrity sha512-dgMN+syt1xb7Hk8LU6AODOfPlvz5z1CbXpPuJE5ZrX9STfBOIXF09pEB8N7a97WT9dbngt3ksDCm6GW6yMrxfQ== + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -1458,7 +1463,7 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/json-schema@^7.0.7": +"@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== @@ -1525,75 +1530,75 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^4.17.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" - integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== +"@typescript-eslint/eslint-plugin@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.6.0.tgz#efd8668b3d6627c46ce722c2afe813928fe120a0" + integrity sha512-MIbeMy5qfLqtgs1hWd088k1hOuRsN9JrHUPwVVKCD99EOUqScd7SrwoZl4Gso05EAP9w1kvLWUVGJOVpRPkDPA== dependencies: - "@typescript-eslint/experimental-utils" "4.33.0" - "@typescript-eslint/scope-manager" "4.33.0" - debug "^4.3.1" + "@typescript-eslint/experimental-utils" "5.6.0" + "@typescript-eslint/scope-manager" "5.6.0" + debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" - regexpp "^3.1.0" + regexpp "^3.2.0" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" - integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== +"@typescript-eslint/experimental-utils@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.6.0.tgz#f3a5960f2004abdcac7bb81412bafc1560841c23" + integrity sha512-VDoRf3Qj7+W3sS/ZBXZh3LBzp0snDLEgvp6qj0vOAIiAPM07bd5ojQ3CTzF/QFl5AKh7Bh1ycgj6lFBJHUt/DA== dependencies: - "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.6.0" + "@typescript-eslint/types" "5.6.0" + "@typescript-eslint/typescript-estree" "5.6.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/parser@^4.17.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" - integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== - dependencies: - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" - debug "^4.3.1" - -"@typescript-eslint/scope-manager@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" - integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== - dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - -"@typescript-eslint/types@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" - integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== - -"@typescript-eslint/typescript-estree@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" - integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== - dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" +"@typescript-eslint/parser@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.6.0.tgz#11677324659641400d653253c03dcfbed468d199" + integrity sha512-YVK49NgdUPQ8SpCZaOpiq1kLkYRPMv9U5gcMrywzI8brtwZjr/tG3sZpuHyODt76W/A0SufNjYt9ZOgrC4tLIQ== + dependencies: + "@typescript-eslint/scope-manager" "5.6.0" + "@typescript-eslint/types" "5.6.0" + "@typescript-eslint/typescript-estree" "5.6.0" + debug "^4.3.2" + +"@typescript-eslint/scope-manager@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.6.0.tgz#9dd7f007dc8f3a34cdff6f79f5eaab27ae05157e" + integrity sha512-1U1G77Hw2jsGWVsO2w6eVCbOg0HZ5WxL/cozVSTfqnL/eB9muhb8THsP0G3w+BB5xAHv9KptwdfYFAUfzcIh4A== + dependencies: + "@typescript-eslint/types" "5.6.0" + "@typescript-eslint/visitor-keys" "5.6.0" + +"@typescript-eslint/types@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.6.0.tgz#745cb1b59daadcc1f32f7be95f0f68accf38afdd" + integrity sha512-OIZffked7mXv4mXzWU5MgAEbCf9ecNJBKi+Si6/I9PpTaj+cf2x58h2oHW5/P/yTnPkKaayfjhLvx+crnl5ubA== + +"@typescript-eslint/typescript-estree@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.6.0.tgz#dfbb19c9307fdd81bd9c650c67e8397821d7faf0" + integrity sha512-92vK5tQaE81rK7fOmuWMrSQtK1IMonESR+RJR2Tlc7w4o0MeEdjgidY/uO2Gobh7z4Q1hhS94Cr7r021fMVEeA== + dependencies: + "@typescript-eslint/types" "5.6.0" + "@typescript-eslint/visitor-keys" "5.6.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" - integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== +"@typescript-eslint/visitor-keys@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.6.0.tgz#3e36509e103fe9713d8f035ac977235fd63cb6e6" + integrity sha512-1p7hDp5cpRFUyE3+lvA74egs+RWSgumrBpzBCDzfTFv0aQ7lIeay80yU0hIxgAhwQ6PcasW35kaOCyDOv6O/Ng== dependencies: - "@typescript-eslint/types" "4.33.0" - eslint-visitor-keys "^2.0.0" + "@typescript-eslint/types" "5.6.0" + eslint-visitor-keys "^3.0.0" JSONStream@^1.0.3: version "1.3.5" @@ -2874,7 +2879,7 @@ de-indent@^1.0.2: resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -2895,6 +2900,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + decamelize@^1.0.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -3336,6 +3348,11 @@ eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== +eslint-visitor-keys@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" + integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== + eslint@7.18.0: version "7.18.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67" @@ -3908,7 +3925,7 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" -globby@^11.0.3: +globby@^11.0.4: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== @@ -6573,7 +6590,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexpp@^3.1.0: +regexpp@^3.1.0, regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== @@ -7548,10 +7565,10 @@ typescript@^3.2.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== -typescript@^4.1.3: - version "4.4.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" - integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== +typescript@^4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.3.tgz#afaa858e68c7103317d89eb90c5d8906268d353c" + integrity sha512-eVYaEHALSt+s9LbvgEv4Ef+Tdq7hBiIZgii12xXJnukryt3pMgJf6aKhoCZ3FWQsu6sydEnkg11fYXLzhLBjeQ== typeson-registry@^1.0.0-alpha.20: version "1.0.0-alpha.39"