diff --git a/package.json b/package.json index 8cc629d91..4f6059a3c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js", "gendoc": "jsdoc -c jsdoc.json -P package.json", "lint": "yarn lint:types && yarn lint:js", - "lint:js": "eslint --max-warnings 4 src spec", + "lint:js": "eslint --max-warnings 0 src spec", "lint:js-fix": "eslint --fix src spec", "lint:types": "tsc --noEmit", "test": "jest", @@ -77,6 +77,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.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", diff --git a/spec/integ/matrix-client-event-timeline.spec.js b/spec/integ/matrix-client-event-timeline.spec.js index c8ec42c74..199f48077 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/src/client.ts b/src/client.ts index 1171ddbee..23c5540b4 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,18 @@ 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_UNSTABLE, retryNetworkOperation, + UploadContentResponseType, } from "./http-api"; import { Crypto, @@ -81,7 +85,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 { @@ -154,7 +157,7 @@ import { MediaHandler } from "./webrtc/mediaHandler"; 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; @@ -207,7 +210,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; @@ -256,7 +259,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 @@ -1113,9 +1116,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, { @@ -1154,9 +1157,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, { @@ -1189,10 +1192,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); } /** @@ -1451,7 +1451,7 @@ export class MatrixClient extends EventEmitter { } return this.http.authedRequest( - undefined, "GET", "/capabilities", + undefined, Method.Get, "/capabilities", ).catch((e: Error): void => { // We swallow errors because we need a default object anyhow logger.error(e); @@ -2453,7 +2453,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) { @@ -2579,7 +2579,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"); @@ -2611,8 +2610,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 }, ); @@ -2644,7 +2643,7 @@ export class MatrixClient extends EventEmitter { }); return this.http.authedRequest( - undefined, "DELETE", path, undefined, undefined, + undefined, Method.Delete, path, undefined, undefined, { prefix: PREFIX_UNSTABLE }, ); } @@ -2683,7 +2682,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 }, ); } @@ -2893,7 +2892,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; @@ -2951,7 +2950,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 }, ); } @@ -3018,7 +3017,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, }, ); @@ -3108,7 +3107,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); @@ -3150,7 +3149,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') { @@ -3224,7 +3223,7 @@ export class MatrixClient extends EventEmitter { if (opts.inviteSignUrl) { signPromise = this.http.requestOtherUrl( - undefined, 'POST', + undefined, Method.Post, opts.inviteSignUrl, { mxid: this.credentials.userId }, ); } @@ -3244,7 +3243,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); @@ -3330,7 +3329,7 @@ export class MatrixClient extends EventEmitter { $roomId: roomId, }); return this.http.authedRequest( - callback, "GET", path, undefined, + callback, Method.Get, path, undefined, ); } @@ -3348,7 +3347,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); } /** @@ -3364,9 +3363,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); } /** @@ -3388,7 +3385,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); } /** @@ -3420,7 +3417,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); } /** @@ -3703,8 +3700,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; @@ -4169,7 +4166,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) { @@ -4286,9 +4283,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: String(ts), }, undefined, { prefix: PREFIX_MEDIA_R0, }, @@ -4321,7 +4318,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); } /** @@ -4465,7 +4462,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); } /** @@ -4581,7 +4578,7 @@ export class MatrixClient extends EventEmitter { user_id: userId, }; return this.http.authedRequest( - callback, "POST", path, undefined, data, + callback, Method.Post, path, undefined, data, ); } @@ -4602,7 +4599,7 @@ export class MatrixClient extends EventEmitter { reason: reason, }; return this.http.authedRequest( - callback, "POST", path, undefined, data, + callback, Method.Post, path, undefined, data, ); } @@ -4634,7 +4631,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, }); @@ -4657,7 +4654,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, }, @@ -4694,7 +4691,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); } /** @@ -4797,7 +4794,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, ); } @@ -4812,7 +4809,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); } /** @@ -4958,7 +4955,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?"); } @@ -5027,9 +5024,9 @@ export class MatrixClient extends EventEmitter { if (limit === undefined) { limit = 30; } - const params: Record = { + const params: Record = { from: fromToken, - limit: limit, + limit: String(limit), dir: dir, }; @@ -5048,7 +5045,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); } /** @@ -5108,8 +5105,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 = []; @@ -5501,7 +5498,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); } /** @@ -5783,7 +5780,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, @@ -5815,9 +5813,9 @@ export class MatrixClient extends EventEmitter { $filterId: filterId, }); - return this.http.authedRequest( - undefined, "GET", path, undefined, undefined, - ).then((response: IFilterDefinition) => { + return this.http.authedRequest( + undefined, Method.Get, path, undefined, undefined, + ).then((response) => { // persist the filter const filter = Filter.fromJson(userId, filterId, response); this.store.storeFilter(filter); @@ -5893,7 +5891,7 @@ export class MatrixClient extends EventEmitter { }); return this.http.authedRequest( - undefined, "POST", path, undefined, {}, + undefined, Method.Post, path, undefined, {}, ); } @@ -5910,7 +5908,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"); } /** @@ -6008,7 +6006,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 } @@ -6024,7 +6022,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: '' }); } /** @@ -6039,7 +6037,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: '' }, ); } @@ -6092,8 +6090,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; @@ -6109,9 +6107,9 @@ 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 { @@ -6130,7 +6128,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(); @@ -6138,7 +6136,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 { @@ -6439,9 +6437,9 @@ 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 }, - ).then((response: { available: boolean }) => { + return this.http.authedRequest<{ available: true }>( + undefined, Method.Get, '/register/available', { username: username }, + ).then((response) => { return response.available; }); } @@ -6561,7 +6559,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); } /** @@ -6570,7 +6568,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"); } /** @@ -6600,7 +6598,7 @@ export class MatrixClient extends EventEmitter { if (callback) { callback(error, response); } - }, "POST", "/login", undefined, loginData, + }, Method.Post, "/login", undefined, loginData, ); } @@ -6679,7 +6677,7 @@ export class MatrixClient extends EventEmitter { */ public logout(callback?: Callback): Promise<{}> { return this.http.authedRequest( - callback, "POST", '/logout', + callback, Method.Post, '/logout', ); } @@ -6708,7 +6706,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); } /** @@ -6765,7 +6763,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); } /** @@ -6798,7 +6796,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, }, ); @@ -6812,7 +6810,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); } /** @@ -6835,7 +6833,7 @@ export class MatrixClient extends EventEmitter { $eventId: eventId, }, ); - return this.http.authedRequest(callback, "GET", path); + return this.http.authedRequest(callback, Method.Get, path); } /** @@ -6869,7 +6867,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); } /** @@ -6885,7 +6883,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 }, ); } @@ -6914,7 +6912,7 @@ export class MatrixClient extends EventEmitter { path = utils.encodeUri(path + "/$stateKey", pathParams); } return this.http.authedRequest( - callback, "GET", path, + callback, Method.Get, path, ); } @@ -6943,7 +6941,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); } /** @@ -6964,9 +6962,7 @@ export class MatrixClient extends EventEmitter { if (!limit) { limit = 30; } - return this.http.authedRequest( - callback, "GET", path, { limit: limit }, - ); + return this.http.authedRequest(callback, Method.Get, path, { limit: String(limit) }); } /** @@ -7000,7 +6996,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); } /** @@ -7009,7 +7005,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); } /** @@ -7023,7 +7019,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); } /** @@ -7055,9 +7051,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); } } @@ -7076,7 +7072,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); } /** @@ -7091,7 +7087,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); } /** @@ -7104,7 +7100,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 }); } /** @@ -7122,7 +7118,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); } /** @@ -7135,7 +7131,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); } /** @@ -7149,7 +7145,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); } /** @@ -7166,7 +7162,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 }); } /** @@ -7193,7 +7189,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 }, ); } @@ -7214,7 +7210,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); } /** @@ -7257,11 +7253,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); } /** @@ -7281,7 +7277,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(); } @@ -7309,7 +7305,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); } /** @@ -7319,7 +7315,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); } /** @@ -7342,7 +7338,7 @@ export class MatrixClient extends EventEmitter { 'bind': bind, }; return this.http.authedRequest( - callback, "POST", path, null, data, + callback, Method.Post, path, null, data, ); } @@ -7361,7 +7357,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 }); } /** @@ -7383,7 +7379,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 }, ); } @@ -7410,7 +7406,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 }); } /** @@ -7427,7 +7423,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 }); } /** @@ -7446,7 +7442,7 @@ export class MatrixClient extends EventEmitter { }; return this.http.authedRequest( - callback, "POST", path, null, data, + callback, Method.Post, path, null, data, ); } @@ -7456,7 +7452,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); } /** @@ -7469,7 +7465,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); } /** @@ -7486,7 +7482,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); } /** @@ -7508,7 +7504,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); } /** @@ -7527,7 +7523,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); } /** @@ -7539,7 +7535,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); } /** @@ -7552,7 +7548,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); } /** @@ -7562,7 +7558,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); }); } @@ -7588,7 +7584,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); } /** @@ -7610,7 +7606,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); } /** @@ -7635,7 +7631,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 }, ); } @@ -7661,7 +7657,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 }, ); } @@ -7682,7 +7678,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); } /** @@ -7703,12 +7699,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, }, @@ -7745,7 +7741,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); } /** @@ -7784,7 +7780,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); } /** @@ -7804,14 +7800,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, }, ); @@ -7837,7 +7833,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, ); } @@ -7877,12 +7873,12 @@ export class MatrixClient extends EventEmitter { const params = { client_secret: clientSecret, email: email, - send_attempt: sendAttempt, + send_attempt: String(sendAttempt), next_link: nextLink, }; return await this.http.idServerRequest( - callback, "POST", "/validate/email/requestToken", + callback, Method.Post, "/validate/email/requestToken", params, PREFIX_IDENTITY_V2, identityAccessToken, ); } @@ -7927,12 +7923,12 @@ export class MatrixClient extends EventEmitter { client_secret: clientSecret, country: phoneCountry, phone_number: phoneNumber, - send_attempt: sendAttempt, + send_attempt: String(sendAttempt), next_link: nextLink, }; return await this.http.idServerRequest( - callback, "POST", "/validate/msisdn/requestToken", + callback, Method.Post, "/validate/msisdn/requestToken", params, PREFIX_IDENTITY_V2, identityAccessToken, ); } @@ -7969,7 +7965,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, ); } @@ -8005,7 +8001,7 @@ export class MatrixClient extends EventEmitter { }; return this.http.requestOtherUrl( - undefined, "POST", url, undefined, params, + undefined, Method.Post, url, undefined, params, ); } @@ -8017,7 +8013,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, ); } @@ -8086,7 +8082,7 @@ export class MatrixClient extends EventEmitter { } const response = await this.http.idServerRequest( - undefined, "POST", "/lookup", + undefined, Method.Post, "/lookup", params, PREFIX_IDENTITY_V2, identityAccessToken, ); @@ -8204,7 +8200,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, ); } @@ -8239,7 +8235,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); } /** @@ -8248,8 +8244,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') { @@ -8275,7 +8271,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); } /** @@ -8291,12 +8287,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( @@ -8309,7 +8305,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 }); } /** @@ -8326,7 +8322,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 }); } /** @@ -8352,7 +8348,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, @@ -8387,11 +8383,14 @@ export class MatrixClient extends EventEmitter { $roomId: roomId, }); - return this.http.authedRequest(undefined, "GET", path, { - suggested_only: suggestedOnly, - max_depth: maxDepth, + return this.http.authedRequest<{ + rooms: IHierarchyRoom[]; + next_batch?: string; // eslint-disable-line camelcase + }>(undefined, Method.Get, path, { + suggested_only: String(suggestedOnly), + max_depth: String(maxDepth), from: fromToken, - limit, + limit: String(limit), }, undefined, { prefix: "/_matrix/client/unstable/org.matrix.msc2946", }).catch(e => { @@ -8498,7 +8497,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); } /** @@ -8509,7 +8508,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); } /** @@ -8526,7 +8525,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, ); } @@ -8546,7 +8545,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, }, ); @@ -8560,7 +8559,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); } /** @@ -8571,7 +8570,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); } /** @@ -8582,7 +8581,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); } /** @@ -8597,7 +8596,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, {}); } /** @@ -8612,7 +8611,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, {}); } /** @@ -8630,7 +8629,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, {}); } /** @@ -8645,7 +8644,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, {}); } /** @@ -8663,7 +8662,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, {}); } /** @@ -8678,7 +8677,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, {}); } /** @@ -8697,7 +8696,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" } }, ); } @@ -8720,7 +8719,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" }, ); } @@ -8737,7 +8736,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, {}); } /** @@ -8752,7 +8751,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 || {}); } /** @@ -8766,7 +8765,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, {}); } /** @@ -8780,7 +8779,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, {}); } /** @@ -8790,7 +8789,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); } /** @@ -8804,7 +8803,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, ); } @@ -8825,7 +8824,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 }, ); } @@ -8841,7 +8840,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, }); } @@ -8861,7 +8860,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", }); @@ -8925,7 +8924,7 @@ 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"); } } diff --git a/src/crypto/EncryptionSetup.ts b/src/crypto/EncryptionSetup.ts index 88bf7b990..27bcf7d78 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 295b48e9f..5c19d166f 100644 --- a/src/crypto/backup.ts +++ b/src/crypto/backup.ts @@ -186,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) { diff --git a/src/crypto/dehydration.ts b/src/crypto/dehydration.ts index 652f72e3e..492344818 100644 --- a/src/crypto/dehydration.ts +++ b/src/crypto/dehydration.ts @@ -22,6 +22,7 @@ import { decryptAES, encryptAES } from './aes'; import { logger } from '../logger'; import { ISecretStorageKeyInfo } from "./api"; import { Crypto } from "./index"; +import { Method } from "../http-api"; import { ISignatures } from "../@types/signed"; export interface IDehydratedDevice { @@ -205,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, { @@ -270,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/http-api.js b/src/http-api.ts similarity index 69% rename from src/http-api.js rename to src/http-api.ts index 3b73484d1..9e32209a1 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: @@ -61,10 +68,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 +169,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 +178,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 +250,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 +268,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 +278,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 +328,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 +337,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 +350,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 +388,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 +407,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 +416,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 +493,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 +534,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 +546,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, - }; + requestOpts = { + localTimeoutMs: opts as number, + } as O; } - if (!opts) { - opts = {}; + + if (!requestOpts.headers) { + requestOpts.headers = {}; } - if (!opts.headers) { - opts.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 +595,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 +615,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 +627,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 +658,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 +670,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 +700,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 +733,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 +777,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 +797,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 +808,7 @@ MatrixHttpApi.prototype = { }; resetTimeout(); - const reqPromise = defer.promise; + const reqPromise = defer.promise as IAbortablePromise>; try { req = this.opts.request( @@ -727,7 +825,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 +833,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 +852,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 +863,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 +893,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 +911,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 +928,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 +959,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 +989,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 +1010,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 +1028,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 +1049,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 +1075,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 eb00c75db..e55aa1d2c 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 46dd52b9a..d074d87c7 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 d17b4b589..0426d596e 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 @@ -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 d22082b53..166514744 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -20,7 +20,7 @@ limitations under the License. import { EventEmitter } from "events"; -import { EventTimelineSet, DuplicateStrategy } from "./event-timeline-set"; +import { DuplicateStrategy, EventTimelineSet } from "./event-timeline-set"; import { EventTimeline } from "./event-timeline"; import { getHttpUriForMxc } from "../content-repo"; import * as utils from "../utils"; @@ -36,6 +36,7 @@ import { GuestAccess, HistoryVisibility, JoinRule, ResizeMethod } from "../@type import { Filter } from "../filter"; import { RoomState } from "./room-state"; import { Thread, ThreadEvent } 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 @@ -660,7 +661,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/scheduler.ts b/src/scheduler.ts index d0436bd83..d0249b6cc 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 7dcd1339d..91d5f31e5 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 2b583eeb7..f870284f6 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -37,20 +37,20 @@ import { IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client" 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 = []; @@ -400,9 +406,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) { @@ -865,8 +872,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, ); } @@ -901,7 +908,7 @@ export class SyncApi { }; if (this.opts.disablePresence) { - qps.set_presence = "offline"; + qps.set_presence = SetPresence.Offline; } if (syncToken) { @@ -924,7 +931,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) { @@ -1477,7 +1484,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/yarn.lock b/yarn.lock index a8491a94b..d5b92f954 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"