1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-06 12:02:40 +03:00

Convert http-api to Typescript (#2063)

This commit is contained in:
Michael Telatynski
2021-12-14 15:34:50 +00:00
committed by GitHub
parent 963c7690b6
commit feb83ba161
15 changed files with 559 additions and 431 deletions

View File

@@ -15,7 +15,7 @@
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js", "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", "gendoc": "jsdoc -c jsdoc.json -P package.json",
"lint": "yarn lint:types && yarn lint:js", "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:js-fix": "eslint --fix src spec",
"lint:types": "tsc --noEmit", "lint:types": "tsc --noEmit",
"test": "jest", "test": "jest",
@@ -77,6 +77,7 @@
"@babel/register": "^7.12.10", "@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", "@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/bs58": "^4.0.1",
"@types/content-type": "^1.1.5",
"@types/jest": "^26.0.20", "@types/jest": "^26.0.20",
"@types/node": "12", "@types/node": "12",
"@types/request": "^2.48.5", "@types/request": "^2.48.5",

View File

@@ -502,7 +502,7 @@ describe("MatrixClient event timelines", function() {
const params = req.queryParams; const params = req.queryParams;
expect(params.dir).toEqual("b"); expect(params.dir).toEqual("b");
expect(params.from).toEqual("start_token0"); expect(params.from).toEqual("start_token0");
expect(params.limit).toEqual(30); expect(params.limit).toEqual("30");
}).respond(200, function() { }).respond(200, function() {
return { return {
chunk: [EVENTS[1], EVENTS[2]], chunk: [EVENTS[1], EVENTS[2]],
@@ -553,7 +553,7 @@ describe("MatrixClient event timelines", function() {
const params = req.queryParams; const params = req.queryParams;
expect(params.dir).toEqual("f"); expect(params.dir).toEqual("f");
expect(params.from).toEqual("end_token0"); expect(params.from).toEqual("end_token0");
expect(params.limit).toEqual(20); expect(params.limit).toEqual("20");
}).respond(200, function() { }).respond(200, function() {
return { return {
chunk: [EVENTS[1], EVENTS[2]], chunk: [EVENTS[1], EVENTS[2]],

File diff suppressed because it is too large Load Diff

View File

@@ -20,15 +20,9 @@ import { logger } from "../logger";
import { MatrixEvent } from "../models/event"; import { MatrixEvent } from "../models/event";
import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning"; import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; 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 { Crypto, IBootstrapCrossSigningOpts } from "./index";
import { import { CrossSigningKeys, ICrossSigningKey, ICryptoCallbacks, ISignedKey, KeySignatures } from "../matrix";
CrossSigningKeys,
ICrossSigningKey,
ICryptoCallbacks,
ISignedKey,
KeySignatures,
} from "../matrix";
import { ISecretStorageKeyInfo } from "./api"; import { ISecretStorageKeyInfo } from "./api";
import { IKeyBackupInfo } from "./keybackup"; import { IKeyBackupInfo } from "./keybackup";
@@ -239,7 +233,7 @@ export class EncryptionSetupOperation {
// Sign the backup with the cross signing key so the key backup can // Sign the backup with the cross signing key so the key backup can
// be trusted via cross-signing. // be trusted via cross-signing.
await baseApis.http.authedRequest( await baseApis.http.authedRequest(
undefined, "PUT", "/room_keys/version/" + this.keyBackupInfo.version, undefined, Method.Put, "/room_keys/version/" + this.keyBackupInfo.version,
undefined, { undefined, {
algorithm: this.keyBackupInfo.algorithm, algorithm: this.keyBackupInfo.algorithm,
auth_data: this.keyBackupInfo.auth_data, auth_data: this.keyBackupInfo.auth_data,
@@ -249,7 +243,7 @@ export class EncryptionSetupOperation {
} else { } else {
// add new key backup // add new key backup
await baseApis.http.authedRequest( await baseApis.http.authedRequest(
undefined, "POST", "/room_keys/version", undefined, Method.Post, "/room_keys/version",
undefined, this.keyBackupInfo, undefined, this.keyBackupInfo,
{ prefix: PREFIX_UNSTABLE }, { prefix: PREFIX_UNSTABLE },
); );

View File

@@ -186,7 +186,6 @@ export class BackupManager {
public async prepareKeyBackupVersion( public async prepareKeyBackupVersion(
key?: string | Uint8Array | null, key?: string | Uint8Array | null,
algorithm?: string | undefined, algorithm?: string | undefined,
// eslint-disable-next-line camelcase
): Promise<IPreparedKeyBackupVersion> { ): Promise<IPreparedKeyBackupVersion> {
const Algorithm = algorithm ? algorithmsByName[algorithm] : DefaultAlgorithm; const Algorithm = algorithm ? algorithmsByName[algorithm] : DefaultAlgorithm;
if (!Algorithm) { if (!Algorithm) {

View File

@@ -22,6 +22,7 @@ import { decryptAES, encryptAES } from './aes';
import { logger } from '../logger'; import { logger } from '../logger';
import { ISecretStorageKeyInfo } from "./api"; import { ISecretStorageKeyInfo } from "./api";
import { Crypto } from "./index"; import { Crypto } from "./index";
import { Method } from "../http-api";
import { ISignatures } from "../@types/signed"; import { ISignatures } from "../@types/signed";
export interface IDehydratedDevice { export interface IDehydratedDevice {
@@ -205,9 +206,10 @@ export class DehydrationManager {
} }
logger.log("Uploading account to server"); 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, undefined,
"PUT", Method.Put,
"/dehydrated_device", "/dehydrated_device",
undefined, undefined,
{ {
@@ -270,7 +272,7 @@ export class DehydrationManager {
logger.log("Uploading keys to server"); logger.log("Uploading keys to server");
await this.crypto.baseApis.http.authedRequest( await this.crypto.baseApis.http.authedRequest(
undefined, undefined,
"POST", Method.Post,
"/keys/upload/" + encodeURI(deviceId), "/keys/upload/" + encodeURI(deviceId),
undefined, undefined,
{ {

View File

@@ -1,6 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 * @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 type { IncomingHttpHeaders, IncomingMessage } from "http";
import { logger } from './logger'; import type { Request as _Request, CoreOptions } from "request";
// we use our own implementation of setTimeout, so that if we get suspended in // 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 // the middle of a /sync, we cancel the sync as soon as we awake, rather than
// waiting for the delay to elapse. // waiting for the delay to elapse.
import * as callbacks from "./realtime-callbacks"; 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: TODO:
@@ -61,10 +68,95 @@ export const PREFIX_IDENTITY_V2 = "/_matrix/identity/v2";
*/ */
export const PREFIX_MEDIA_R0 = "/_matrix/media/r0"; 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<string, string>;
localTimeoutMs?: number;
useAuthorizationHeader?: boolean;
request(opts: Pick<CoreOptions, RequestProps> & {
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<T> {
prefix?: string;
localTimeoutMs?: number;
headers?: Record<string, string>;
json?: boolean; // defaults to true
qsStringifyOptions?: CoreOptions["qsStringifyOptions"];
bodyParser?(body: string): T;
}
export interface IUpload {
loaded: number;
total: number;
promise: IAbortablePromise<unknown>;
}
interface IContentUri {
base: string;
path: string;
params: {
// eslint-disable-next-line camelcase
access_token: string;
};
}
type ResponseType<T, O extends IRequestOpts<T> | 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 IUploadOpts> =
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. * Construct a MatrixHttpApi.
* @constructor * @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 {Object} opts The options to use for this HTTP API.
* @param {string} opts.baseUrl Required. The base client-server URL e.g. * @param {string} opts.baseUrl Required. The base client-server URL e.g.
* 'http://localhost:8008'. * '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 * response (e.g. the parsed HTTP body). If false, requests will return an
* object with the properties <tt>code</tt>, <tt>headers</tt> and <tt>data</tt>. * object with the properties <tt>code</tt>, <tt>headers</tt> and <tt>data</tt>.
* *
* @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. * null to not send an access token.
* @param {Object=} opts.extraParams Optional. Extra query parameters to send on * @param {Object=} opts.extraParams Optional. Extra query parameters to send on
* requests. * requests.
@@ -86,39 +178,37 @@ export const PREFIX_MEDIA_R0 = "/_matrix/media/r0";
* @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use * @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use
* Authorization header instead of query param to send the access token to the server. * Authorization header instead of query param to send the access token to the server.
*/ */
export function MatrixHttpApi(event_emitter, opts) { export class MatrixHttpApi {
utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]); private uploads: IUpload[] = [];
opts.onlyData = opts.onlyData || false;
this.event_emitter = event_emitter; constructor(private eventEmitter: EventEmitter, public readonly opts: IHttpOpts) {
this.opts = opts; utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]);
this.useAuthorizationHeader = Boolean(opts.useAuthorizationHeader); opts.onlyData = !!opts.onlyData;
this.uploads = []; 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 * @param {string} url The new base url
*/ */
setIdBaseUrl: function(url) { public setIdBaseUrl(url: string): void {
this.opts.idBaseUrl = url; this.opts.idBaseUrl = url;
}, }
/** /**
* Get the content repository url with query parameters. * Get the content repository url with query parameters.
* @return {Object} An object with a 'base', 'path' and 'params' for base URL, * @return {Object} An object with a 'base', 'path' and 'params' for base URL,
* path and query parameters respectively. * path and query parameters respectively.
*/ */
getContentUri: function() { public getContentUri(): IContentUri {
const params = {
access_token: this.opts.accessToken,
};
return { return {
base: this.opts.baseUrl, base: this.opts.baseUrl,
path: "/_matrix/media/r0/upload", path: "/_matrix/media/r0/upload",
params: params, params: {
}; access_token: this.opts.accessToken,
}, },
};
}
/** /**
* Upload content to the homeserver * Upload content to the homeserver
@@ -160,14 +250,17 @@ MatrixHttpApi.prototype = {
* determined by this.opts.onlyData, opts.rawResponse, and * determined by this.opts.onlyData, opts.rawResponse, and
* opts.onlyContentUri. Rejects with an error (usually a MatrixError). * opts.onlyContentUri. Rejects with an error (usually a MatrixError).
*/ */
uploadContent: function(file, opts) { public uploadContent<O extends IUploadOpts>(
file: FileType,
opts?: O,
): IAbortablePromise<UploadContentResponseType<O>> {
if (utils.isFunction(opts)) { if (utils.isFunction(opts)) {
// opts used to be callback // opts used to be callback, backwards compatibility
opts = { opts = {
callback: opts, callback: opts as unknown as IUploadOpts["callback"],
}; } as O;
} else if (opts === undefined) { } else if (!opts) {
opts = {}; opts = {} as O;
} }
// default opts.includeFilename to true (ignoring falsey values) // 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 // if the file doesn't have a mime type, use a default since
// the HS errors if we don't supply one. // the HS errors if we don't supply one.
const contentType = opts.type || file.type || 'application/octet-stream'; const contentType = opts.type || (file as File).type || 'application/octet-stream';
const fileName = opts.name || file.name; const fileName = opts.name || (file as File).name;
// We used to recommend setting file.stream to the thing to upload on // 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 // 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 // the browser now define a `stream` method, which leads to trouble
// here, so we also check the type of `stream`. // here, so we also check the type of `stream`.
let body = file; 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( logger.warn(
"Using `file.stream` as the content to upload. Future " + "Using `file.stream` as the content to upload. Future " +
"versions of the js-sdk will change this to expect `file` to " + "versions of the js-sdk will change this to expect `file` to " +
"be the content directly.", "be the content directly.",
); );
body = body.stream; body = bodyStream;
} }
// backwards-compatibility hacks where we used to do different things // 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 // (browser-request doesn't support progress either, which is also kind
// of important here) // of important here)
const upload = { loaded: 0, total: 0 }; const upload = { loaded: 0, total: 0 } as IUpload;
let promise; let promise: IAbortablePromise<UploadContentResponseType<O>>;
// XMLHttpRequest doesn't parse JSON for us. request normally does, but // 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 // 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. // way, we have to JSON-parse the response ourselves.
let bodyParser = null; let bodyParser = null;
if (!rawResponse) { if (!rawResponse) {
bodyParser = function(rawBody) { bodyParser = function(rawBody: string) {
let body = JSON.parse(rawBody); let body = JSON.parse(rawBody);
if (onlyContentUri) { if (onlyContentUri) {
body = body.content_uri; body = body.content_uri;
@@ -256,25 +350,23 @@ MatrixHttpApi.prototype = {
} }
if (global.XMLHttpRequest) { if (global.XMLHttpRequest) {
const defer = utils.defer(); const defer = utils.defer<UploadContentResponseType<O>>();
const xhr = new global.XMLHttpRequest(); const xhr = new global.XMLHttpRequest();
upload.xhr = xhr;
const cb = requestCallback(defer, opts.callback, this.opts.onlyData); const cb = requestCallback(defer, opts.callback, this.opts.onlyData);
const timeout_fn = function() { const timeoutFn = function() {
xhr.abort(); xhr.abort();
cb(new Error('Timeout')); cb(new Error('Timeout'));
}; };
// set an initial timeout of 30s; we'll advance it each time we get // set an initial timeout of 30s; we'll advance it each time we get a progress notification
// a progress notification let timeoutTimer = callbacks.setTimeout(timeoutFn, 30000);
xhr.timeout_timer = callbacks.setTimeout(timeout_fn, 30000);
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
let resp; let resp: string;
switch (xhr.readyState) { switch (xhr.readyState) {
case global.XMLHttpRequest.DONE: case global.XMLHttpRequest.DONE:
callbacks.clearTimeout(xhr.timeout_timer); callbacks.clearTimeout(timeoutTimer);
try { try {
if (xhr.status === 0) { if (xhr.status === 0) {
throw new AbortError(); throw new AbortError();
@@ -296,10 +388,10 @@ MatrixHttpApi.prototype = {
} }
}; };
xhr.upload.addEventListener("progress", function(ev) { xhr.upload.addEventListener("progress", function(ev) {
callbacks.clearTimeout(xhr.timeout_timer); callbacks.clearTimeout(timeoutTimer);
upload.loaded = ev.loaded; upload.loaded = ev.loaded;
upload.total = ev.total; upload.total = ev.total;
xhr.timeout_timer = callbacks.setTimeout(timeout_fn, 30000); timeoutTimer = callbacks.setTimeout(timeoutFn, 30000);
if (opts.progressHandler) { if (opts.progressHandler) {
opts.progressHandler({ opts.progressHandler({
loaded: ev.loaded, loaded: ev.loaded,
@@ -315,9 +407,8 @@ MatrixHttpApi.prototype = {
queryArgs.push("filename=" + encodeURIComponent(fileName)); queryArgs.push("filename=" + encodeURIComponent(fileName));
} }
if (!this.useAuthorizationHeader) { if (!this.opts.useAuthorizationHeader) {
queryArgs.push("access_token=" queryArgs.push("access_token=" + encodeURIComponent(this.opts.accessToken));
+ encodeURIComponent(this.opts.accessToken));
} }
if (queryArgs.length > 0) { if (queryArgs.length > 0) {
@@ -325,73 +416,69 @@ MatrixHttpApi.prototype = {
} }
xhr.open("POST", url); xhr.open("POST", url);
if (this.useAuthorizationHeader) { if (this.opts.useAuthorizationHeader) {
xhr.setRequestHeader("Authorization", "Bearer " + this.opts.accessToken); xhr.setRequestHeader("Authorization", "Bearer " + this.opts.accessToken);
} }
xhr.setRequestHeader("Content-Type", contentType); xhr.setRequestHeader("Content-Type", contentType);
xhr.send(body); xhr.send(body);
promise = defer.promise; promise = defer.promise as IAbortablePromise<UploadContentResponseType<O>>;
// 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); promise.abort = xhr.abort.bind(xhr);
} else { } else {
const queryParams = {}; const queryParams: Record<string, string> = {};
if (includeFilename && fileName) { if (includeFilename && fileName) {
queryParams.filename = fileName; queryParams.filename = fileName;
} }
promise = this.authedRequest( promise = this.authedRequest(
opts.callback, "POST", "/upload", queryParams, body, { opts.callback, Method.Post, "/upload", queryParams, body, {
prefix: "/_matrix/media/r0", prefix: "/_matrix/media/r0",
headers: { "Content-Type": contentType }, headers: { "Content-Type": contentType },
json: false, json: false,
bodyParser: bodyParser, bodyParser,
}, },
); );
} }
const self = this;
// remove the upload from the list on completion // remove the upload from the list on completion
const promise0 = promise.finally(function() { upload.promise = promise.finally(() => {
for (let i = 0; i < self.uploads.length; ++i) { for (let i = 0; i < this.uploads.length; ++i) {
if (self.uploads[i] === upload) { if (this.uploads[i] === upload) {
self.uploads.splice(i, 1); this.uploads.splice(i, 1);
return; return;
} }
} }
}); }) as IAbortablePromise<UploadContentResponseType<O>>;
// copy our dirty abort() method to the new promise // copy our dirty abort() method to the new promise
promise0.abort = promise.abort; upload.promise.abort = promise.abort;
upload.promise = promise0;
this.uploads.push(upload); this.uploads.push(upload);
return promise0; return upload.promise as IAbortablePromise<UploadContentResponseType<O>>;
}, }
cancelUpload: function(promise) { public cancelUpload(promise: IAbortablePromise<unknown>): boolean {
if (promise.abort) { if (promise.abort) {
promise.abort(); promise.abort();
return true; return true;
} }
return false; return false;
}, }
getCurrentUploads: function() { public getCurrentUploads(): IUpload[] {
return this.uploads; return this.uploads;
}, }
idServerRequest: function( public idServerRequest<T>(
callback, callback: Callback<T>,
method, method: Method,
path, path: string,
params, params: Record<string, string | string[]>,
prefix, prefix: string,
accessToken, accessToken: string,
) { ): Promise<T> {
if (!this.opts.idBaseUrl) { if (!this.opts.idBaseUrl) {
throw new Error("No identity server base URL set"); throw new Error("No identity server base URL set");
} }
@@ -406,28 +493,27 @@ MatrixHttpApi.prototype = {
const opts = { const opts = {
uri: fullUri, uri: fullUri,
method: method, method,
withCredentials: false, withCredentials: false,
json: true, // we want a JSON response if we can json: true, // we want a JSON response if we can
_matrix_opts: this.opts, _matrix_opts: this.opts,
headers: {}, headers: {},
}; } as Parameters<IHttpOpts["request"]>[0];
if (method === 'GET') {
if (method === Method.Get) {
opts.qs = params; opts.qs = params;
} else if (typeof params === "object") { } else if (typeof params === "object") {
opts.json = params; opts.json = !!params; // XXX: this feels strange
} }
if (accessToken) { if (accessToken) {
opts.headers['Authorization'] = `Bearer ${accessToken}`; opts.headers['Authorization'] = `Bearer ${accessToken}`;
} }
const defer = utils.defer(); const defer = utils.defer<T>();
this.opts.request( this.opts.request(opts, requestCallback(defer, callback, this.opts.onlyData));
opts,
requestCallback(defer, callback, this.opts.onlyData),
);
return defer.promise; return defer.promise;
}, }
/** /**
* Perform an authorised request to the homeserver. * 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 * @param {Number=} opts.localTimeoutMs The maximum amount of time to wait before
* timing out the request. If not specified, there is no timeout. * 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. * "/_matrix/client/v2_alpha". If not specified, uses this.opts.prefix.
* *
* @param {Object=} opts.headers map of additional request headers * @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 * @return {module:http-api.MatrixError} Rejects with an error if a problem
* occurred. This includes network problems and Matrix-specific error JSON. * occurred. This includes network problems and Matrix-specific error JSON.
*/ */
authedRequest: function(callback, method, path, queryParams, data, opts) { public authedRequest<T, O extends IRequestOpts<T> = IRequestOpts<T>>(
if (!queryParams) { callback: Callback<T>,
queryParams = {}; method: Method,
} path: string,
if (this.useAuthorizationHeader) { queryParams?: Record<string, string | string[]>,
if (isFinite(opts)) { data?: CoreOptions["body"],
opts?: O | number, // number is legacy
): IAbortablePromise<ResponseType<T, O>> {
if (!queryParams) queryParams = {};
let requestOpts = (opts || {}) as O;
if (this.opts.useAuthorizationHeader) {
if (isFinite(opts as number)) {
// opts used to be localTimeoutMs // opts used to be localTimeoutMs
opts = { requestOpts = {
localTimeoutMs: opts, localTimeoutMs: opts as number,
}; } as O;
} }
if (!opts) {
opts = {}; if (!requestOpts.headers) {
requestOpts.headers = {};
} }
if (!opts.headers) { if (!requestOpts.headers.Authorization) {
opts.headers = {}; requestOpts.headers.Authorization = "Bearer " + this.opts.accessToken;
}
if (!opts.headers.Authorization) {
opts.headers.Authorization = "Bearer " + this.opts.accessToken;
} }
if (queryParams.access_token) { if (queryParams.access_token) {
delete queryParams.access_token; delete queryParams.access_token;
} }
} else { } else if (!queryParams.access_token) {
if (!queryParams.access_token) {
queryParams.access_token = this.opts.accessToken; queryParams.access_token = this.opts.accessToken;
} }
}
const requestPromise = this.request( const requestPromise = this.request<T, O>(callback, method, path, queryParams, data, requestOpts);
callback, method, path, queryParams, data, opts,
);
const self = this; requestPromise.catch((err: MatrixError) => {
requestPromise.catch(function(err) {
if (err.errcode == 'M_UNKNOWN_TOKEN') { 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') { } else if (err.errcode == 'M_CONSENT_NOT_GIVEN') {
self.event_emitter.emit( this.eventEmitter.emit(
"no_consent", "no_consent",
err.message, err.message,
err.data.consent_uri, err.data.consent_uri,
@@ -509,7 +595,7 @@ MatrixHttpApi.prototype = {
// return the original promise, otherwise tests break due to it having to // 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 // go around the event loop one more time to process the result of the request
return requestPromise; return requestPromise;
}, }
/** /**
* Perform a request to the homeserver without any credentials. * 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 * @param {Number=} opts.localTimeoutMs The maximum amount of time to wait before
* timing out the request. If not specified, there is no timeout. * 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. * "/_matrix/client/v2_alpha". If not specified, uses this.opts.prefix.
* *
* @param {Object=} opts.headers map of additional request headers * @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 * @return {module:http-api.MatrixError} Rejects with an error if a problem
* occurred. This includes network problems and Matrix-specific error JSON. * occurred. This includes network problems and Matrix-specific error JSON.
*/ */
request: function(callback, method, path, queryParams, data, opts) { public request<T, O extends IRequestOpts<T> = IRequestOpts<T>>(
opts = opts || {}; callback: Callback<T>,
const prefix = opts.prefix !== undefined ? opts.prefix : this.opts.prefix; method: Method,
path: string,
queryParams?: CoreOptions["qs"],
data?: CoreOptions["body"],
opts?: O,
): IAbortablePromise<ResponseType<T, O>> {
const prefix = opts?.prefix ?? this.opts.prefix;
const fullUri = this.opts.baseUrl + prefix + path; const fullUri = this.opts.baseUrl + prefix + path;
return this.requestOtherUrl( return this.requestOtherUrl<T, O>(callback, method, fullUri, queryParams, data, opts);
callback, method, fullUri, queryParams, data, opts, }
);
},
/** /**
* Perform a request to an arbitrary URL. * 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 * @param {Number=} opts.localTimeoutMs The maximum amount of time to wait before
* timing out the request. If not specified, there is no timeout. * 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. * "/_matrix/client/v2_alpha". If not specified, uses this.opts.prefix.
* *
* @param {Object=} opts.headers map of additional request headers * @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 * @return {module:http-api.MatrixError} Rejects with an error if a problem
* occurred. This includes network problems and Matrix-specific error JSON. * occurred. This includes network problems and Matrix-specific error JSON.
*/ */
requestOtherUrl: function(callback, method, uri, queryParams, data, public requestOtherUrl<T, O extends IRequestOpts<T> = IRequestOpts<T>>(
opts) { callback: Callback<T>,
if (opts === undefined || opts === null) { method: Method,
opts = {}; uri: string,
} else if (isFinite(opts)) { queryParams?: CoreOptions["qs"],
data?: CoreOptions["body"],
opts?: O | number, // number is legacy
): IAbortablePromise<ResponseType<T, O>> {
let requestOpts = (opts || {}) as O;
if (isFinite(opts as number)) {
// opts used to be localTimeoutMs // opts used to be localTimeoutMs
opts = { requestOpts = {
localTimeoutMs: opts, localTimeoutMs: opts as number,
}; } as O;
} }
return this._request( return this.doRequest<T, O>(callback, method, uri, queryParams, data, requestOpts);
callback, method, uri, queryParams, data, opts, }
);
},
/** /**
* Form and return a homeserver request URL based on the given path * Form and return a homeserver request URL based on the given path
@@ -607,13 +700,13 @@ MatrixHttpApi.prototype = {
* "/_matrix/client/v2_alpha". * "/_matrix/client/v2_alpha".
* @return {string} URL * @return {string} URL
*/ */
getUrl: function(path, queryParams, prefix) { public getUrl(path: string, queryParams: CoreOptions["qs"], prefix: string): string {
let queryString = ""; let queryString = "";
if (queryParams) { if (queryParams) {
queryString = "?" + utils.encodeParams(queryParams); queryString = "?" + utils.encodeParams(queryParams);
} }
return this.opts.baseUrl + prefix + path + queryString; return this.opts.baseUrl + prefix + path + queryString;
}, }
/** /**
* @private * @private
@@ -640,25 +733,32 @@ MatrixHttpApi.prototype = {
* @return {Promise} a promise which resolves to either the * @return {Promise} a promise which resolves to either the
* response object (if this.opts.onlyData is truthy), or the parsed * response object (if this.opts.onlyData is truthy), or the parsed
* body. Rejects * 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<T, O extends IRequestOpts<T> = IRequestOpts<T>>(
callback: Callback<T>,
method: Method,
uri: string,
queryParams?: Record<string, string>,
data?: CoreOptions["body"],
opts?: O,
): IAbortablePromise<ResponseType<T, O>> {
if (callback !== undefined && !utils.isFunction(callback)) { if (callback !== undefined && !utils.isFunction(callback)) {
throw Error( throw Error("Expected callback to be a function but got " + typeof callback);
"Expected callback to be a function but got " + typeof callback,
);
} }
opts = opts || {};
const self = this;
if (this.opts.extraParams) { if (this.opts.extraParams) {
queryParams = { queryParams = {
...queryParams, ...(queryParams || {}),
...this.opts.extraParams, ...this.opts.extraParams,
}; };
} }
const headers = Object.assign({}, opts.headers || {}); 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; let bodyParser = opts.bodyParser;
// we handle the json encoding/decoding here, because request and // we handle the json encoding/decoding here, because request and
@@ -677,17 +777,17 @@ MatrixHttpApi.prototype = {
} }
if (bodyParser === undefined) { if (bodyParser === undefined) {
bodyParser = function(rawBody) { bodyParser = function(rawBody: string) {
return JSON.parse(rawBody); return JSON.parse(rawBody);
}; };
} }
} }
const defer = utils.defer(); const defer = utils.defer<T>();
let timeoutId; let timeoutId: number;
let timedOut = false; let timedOut = false;
let req; let req: IRequest;
const localTimeoutMs = opts.localTimeoutMs || this.opts.localTimeoutMs; const localTimeoutMs = opts.localTimeoutMs || this.opts.localTimeoutMs;
const resetTimeout = () => { const resetTimeout = () => {
@@ -697,9 +797,7 @@ MatrixHttpApi.prototype = {
} }
timeoutId = callbacks.setTimeout(function() { timeoutId = callbacks.setTimeout(function() {
timedOut = true; timedOut = true;
if (req && req.abort) { req?.abort?.();
req.abort();
}
defer.reject(new MatrixError({ defer.reject(new MatrixError({
error: "Locally timed out waiting for a response", error: "Locally timed out waiting for a response",
errcode: "ORG.MATRIX.JSSDK_TIMEOUT", errcode: "ORG.MATRIX.JSSDK_TIMEOUT",
@@ -710,7 +808,7 @@ MatrixHttpApi.prototype = {
}; };
resetTimeout(); resetTimeout();
const reqPromise = defer.promise; const reqPromise = defer.promise as IAbortablePromise<ResponseType<T, O>>;
try { try {
req = this.opts.request( req = this.opts.request(
@@ -727,7 +825,7 @@ MatrixHttpApi.prototype = {
headers: headers || {}, headers: headers || {},
_matrix_opts: this.opts, _matrix_opts: this.opts,
}, },
function(err, response, body) { (err, response, body) => {
if (localTimeoutMs) { if (localTimeoutMs) {
callbacks.clearTimeout(timeoutId); callbacks.clearTimeout(timeoutId);
if (timedOut) { if (timedOut) {
@@ -735,16 +833,13 @@ MatrixHttpApi.prototype = {
} }
} }
const handlerFn = requestCallback( const handlerFn = requestCallback(defer, callback, this.opts.onlyData, bodyParser);
defer, callback, self.opts.onlyData,
bodyParser,
);
handlerFn(err, response, body); handlerFn(err, response, body);
}, },
); );
if (req) { if (req) {
// This will only work in a browser, where opts.request is the // 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. // updates - see https://github.com/request/request/pull/2346.
// `browser-request` returns an XHRHttpRequest which exposes `onprogress` // `browser-request` returns an XHRHttpRequest which exposes `onprogress`
if ('onprogress' in req) { 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 // FIXME: This is EVIL, but I can't think of a better way to expose
// abort() operations on underlying HTTP requests :( // 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) { } catch (ex) {
defer.reject(ex); defer.reject(ex);
@@ -766,8 +863,21 @@ MatrixHttpApi.prototype = {
} }
} }
return reqPromise; 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<T> {
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, * 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) * response, otherwise the result object (with `code` and `data` fields)
* *
*/ */
const requestCallback = function( function requestCallback<T>(
defer, userDefinedCallback, onlyData, defer: IDeferred<T>,
bodyParser, userDefinedCallback?: Callback<T>,
) { onlyData = false,
userDefinedCallback = userDefinedCallback || function() {}; bodyParser?: (body: string) => T,
): RequestCallback {
return function(err, response, body) { return function(err: Error, response: XMLHttpRequest | IncomingMessage, body: string): void {
if (err) { if (err) {
// the unit tests use matrix-mock-request, which throw the string "aborted" when aborting a request. // 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 // 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)) { if (!aborted && !(err instanceof MatrixError)) {
// browser-request just throws normal Error objects, // browser-request just throws normal Error objects,
// not `TypeError`s like fetch does. So just assume any // not `TypeError`s like fetch does. So just assume any
@@ -801,13 +911,15 @@ const requestCallback = function(
err = new ConnectionError("request failed", err); err = new ConnectionError("request failed", err);
} }
} }
let data: T | string = body;
if (!err) { if (!err) {
try { try {
const httpStatus = response.status || response.statusCode; // XMLHttpRequest vs http.IncomingMessage if (getStatusCode(response) >= 400) {
if (httpStatus >= 400) {
err = parseErrorResponse(response, body); err = parseErrorResponse(response, body);
} else if (bodyParser) { } else if (bodyParser) {
body = bodyParser(body); data = bodyParser(body);
} }
} catch (e) { } catch (e) {
err = new Error(`Error parsing server response: ${e}`); err = new Error(`Error parsing server response: ${e}`);
@@ -816,21 +928,26 @@ const requestCallback = function(
if (err) { if (err) {
defer.reject(err); defer.reject(err);
userDefinedCallback(err); userDefinedCallback?.(err);
} else if (onlyData) {
defer.resolve(data as T);
userDefinedCallback?.(null, data as T);
} else { } else {
const res = { const res: IResponse<T> = {
code: response.status || response.statusCode, // XMLHttpRequest vs http.IncomingMessage code: getStatusCode(response),
// XXX: why do we bother with this? it doesn't work for // XXX: why do we bother with this? it doesn't work for
// XMLHttpRequest, so clearly we don't use it. // XMLHttpRequest, so clearly we don't use it.
headers: response.headers, headers: (response as IncomingMessage).headers,
data: body, data: data as T,
}; };
defer.resolve(onlyData ? body : res); // XXX: the variations in caller-expected types here are horrible,
userDefinedCallback(null, onlyData ? body : res); // 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. * 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 * @param {String} body raw body of the response
* @returns {Error} * @returns {Error}
*/ */
function parseErrorResponse(response, body) { function parseErrorResponse(response: XMLHttpRequest | IncomingMessage, body?: string) {
const httpStatus = response.status || response.statusCode; // XMLHttpRequest vs http.IncomingMessage const httpStatus = getStatusCode(response);
const contentType = getResponseContentType(response); const contentType = getResponseContentType(response);
let err; let err;
@@ -872,14 +989,14 @@ function parseErrorResponse(response, body) {
* @param {XMLHttpRequest|http.IncomingMessage} response response object * @param {XMLHttpRequest|http.IncomingMessage} response response object
* @returns {{type: String, parameters: Object}?} parsed content-type header, or null if not found * @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; let contentType;
if (response.getResponseHeader) { if ((response as XMLHttpRequest).getResponseHeader) {
// XMLHttpRequest provides getResponseHeader // XMLHttpRequest provides getResponseHeader
contentType = response.getResponseHeader("Content-Type"); contentType = (response as XMLHttpRequest).getResponseHeader("Content-Type");
} else if (response.headers) { } else if ((response as IncomingMessage).headers) {
// request provides http.IncomingMessage which has a message.headers map // 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) { 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 * Construct a Matrix error. This is a JavaScript Error with additional
* information specific to the standard Matrix error response. * information specific to the standard Matrix error response.
@@ -905,8 +1028,11 @@ function getResponseContentType(response) {
* @prop {integer} httpStatus The numeric HTTP status code given * @prop {integer} httpStatus The numeric HTTP status code given
*/ */
export class MatrixError extends Error { export class MatrixError extends Error {
constructor(errorJson) { public readonly errcode: string;
errorJson = errorJson || {}; public readonly data: IErrorJson;
public httpStatus?: number; // set by http-api
constructor(errorJson: IErrorJson = {}) {
super(`MatrixError: ${errorJson.errcode}`); super(`MatrixError: ${errorJson.errcode}`);
this.errcode = errorJson.errcode; this.errcode = errorJson.errcode;
this.name = errorJson.errcode || "Unknown error code"; this.name = errorJson.errcode || "Unknown error code";
@@ -923,18 +1049,13 @@ export class MatrixError extends Error {
* @constructor * @constructor
*/ */
export class ConnectionError extends Error { export class ConnectionError extends Error {
constructor(message, cause = undefined) { constructor(message: string, private readonly cause: Error = undefined) {
super(message + (cause ? `: ${cause.message}` : "")); super(message + (cause ? `: ${cause.message}` : ""));
this._cause = cause;
} }
get name() { get name() {
return "ConnectionError"; return "ConnectionError";
} }
get cause() {
return this._cause;
}
} }
export class AbortError extends Error { export class AbortError extends Error {
@@ -954,7 +1075,7 @@ export class AbortError extends Error {
* @return {any} the result of the network operation * @return {any} the result of the network operation
* @throws {ConnectionError} If after maxAttempts the callback still throws ConnectionError * @throws {ConnectionError} If after maxAttempts the callback still throws ConnectionError
*/ */
export async function retryNetworkOperation(maxAttempts, callback) { export async function retryNetworkOperation<T>(maxAttempts: number, callback: () => T): Promise<T> {
let attempts = 0; let attempts = 0;
let lastConnectionError = null; let lastConnectionError = null;
while (attempts < maxAttempts) { while (attempts < maxAttempts) {

View File

@@ -21,7 +21,6 @@ limitations under the License.
import { logger } from './logger'; import { logger } from './logger';
import { MatrixClient } from "./client"; import { MatrixClient } from "./client";
import { defer, IDeferred } from "./utils"; import { defer, IDeferred } from "./utils";
import { MatrixError } from "./http-api";
const EMAIL_STAGE_TYPE = "m.login.email.identity"; const EMAIL_STAGE_TYPE = "m.login.email.identity";
const MSISDN_STAGE_TYPE = "m.login.msisdn"; const MSISDN_STAGE_TYPE = "m.login.msisdn";
@@ -49,7 +48,7 @@ export interface IAuthData {
flows?: IFlow[]; flows?: IFlow[];
params?: Record<string, Record<string, any>>; params?: Record<string, Record<string, any>>;
errcode?: string; errcode?: string;
error?: MatrixError; error?: string;
} }
export enum AuthType { export enum AuthType {

View File

@@ -18,7 +18,7 @@ import { MatrixClient } from "../client";
import { IEncryptedFile, RelationType, UNSTABLE_MSC3089_BRANCH } from "../@types/event"; import { IEncryptedFile, RelationType, UNSTABLE_MSC3089_BRANCH } from "../@types/event";
import { IContent, MatrixEvent } from "./event"; import { IContent, MatrixEvent } from "./event";
import { MSC3089TreeSpace } from "./MSC3089TreeSpace"; 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 * 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( public async createNewVersion(
name: string, name: string,
encryptedContents: File | String | Buffer | ReadStream | Blob, encryptedContents: FileType,
info: Partial<IEncryptedFile>, info: Partial<IEncryptedFile>,
additionalContent?: IContent, additionalContent?: IContent,
): Promise<void> { ): Promise<void> {

View File

@@ -32,7 +32,7 @@ import {
import { MSC3089Branch } from "./MSC3089Branch"; import { MSC3089Branch } from "./MSC3089Branch";
import { isRoomSharedHistory } from "../crypto/algorithms/megolm"; import { isRoomSharedHistory } from "../crypto/algorithms/megolm";
import { ISendEventResponse } from "../@types/requests"; 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 * The recommended defaults for a tree space's power levels. Note that this
@@ -472,7 +472,7 @@ export class MSC3089TreeSpace {
*/ */
public async createFile( public async createFile(
name: string, name: string,
encryptedContents: File | String | Buffer | ReadStream | Blob, encryptedContents: FileType,
info: Partial<IEncryptedFile>, info: Partial<IEncryptedFile>,
additionalContent?: IContent, additionalContent?: IContent,
): Promise<ISendEventResponse> { ): Promise<ISendEventResponse> {

View File

@@ -20,7 +20,7 @@ limitations under the License.
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { EventTimelineSet, DuplicateStrategy } from "./event-timeline-set"; import { DuplicateStrategy, EventTimelineSet } from "./event-timeline-set";
import { EventTimeline } from "./event-timeline"; import { EventTimeline } from "./event-timeline";
import { getHttpUriForMxc } from "../content-repo"; import { getHttpUriForMxc } from "../content-repo";
import * as utils from "../utils"; import * as utils from "../utils";
@@ -36,6 +36,7 @@ import { GuestAccess, HistoryVisibility, JoinRule, ResizeMethod } from "../@type
import { Filter } from "../filter"; import { Filter } from "../filter";
import { RoomState } from "./room-state"; import { RoomState } from "./room-state";
import { Thread, ThreadEvent } from "./thread"; import { Thread, ThreadEvent } from "./thread";
import { Method } from "../http-api";
// These constants are used as sane defaults when the homeserver doesn't support // 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 // 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, const path = utils.encodeUri("/rooms/$roomId/members?" + queryString,
{ $roomId: this.roomId }); { $roomId: this.roomId });
const http = this.client.http; 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; return response.chunk;
} }

View File

@@ -70,7 +70,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
} }
// we ship with browser-request which returns { cors: rejected } when trying // 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. // 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; return -1;
} }

View File

@@ -256,8 +256,8 @@ function removeByPrefix(store, prefix) {
} }
} }
function debuglog() { function debuglog(...args) {
if (DEBUG) { if (DEBUG) {
logger.log(...arguments); logger.log(...args);
} }
} }

View File

@@ -37,20 +37,20 @@ import { IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client"
import { SyncState } from "./sync.api"; import { SyncState } from "./sync.api";
import { import {
Category, Category,
IEphemeral,
IInvitedRoom, IInvitedRoom,
IInviteState, IInviteState,
IJoinedRoom, IJoinedRoom,
ILeftRoom, ILeftRoom,
IStateEvent, IMinimalEvent,
IRoomEvent, IRoomEvent,
IStateEvent,
IStrippedState, IStrippedState,
ISyncResponse, ISyncResponse,
ITimeline, ITimeline,
IEphemeral,
IMinimalEvent,
} from "./sync-accumulator"; } from "./sync-accumulator";
import { MatrixEvent } from "./models/event"; import { MatrixEvent } from "./models/event";
import { MatrixError } from "./http-api"; import { MatrixError, Method } from "./http-api";
import { ISavedSync } from "./store"; import { ISavedSync } from "./store";
import { EventType } from "./@types/event"; import { EventType } from "./@types/event";
import { IPushRules } from "./@types/PushRules"; import { IPushRules } from "./@types/PushRules";
@@ -94,6 +94,12 @@ export interface ISyncStateData {
fromCache?: boolean; fromCache?: boolean;
} }
enum SetPresence {
Offline = "offline",
Online = "online",
Unavailable = "unavailable",
}
interface ISyncParams { interface ISyncParams {
filter?: string; filter?: string;
timeout: number; timeout: number;
@@ -101,7 +107,7 @@ interface ISyncParams {
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
full_state?: boolean; full_state?: boolean;
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
set_presence?: "offline" | "online" | "unavailable"; set_presence?: SetPresence;
_cacheBuster?: string | number; // not part of the API itself _cacheBuster?: string | number; // not part of the API itself
} }
@@ -260,12 +266,12 @@ export class SyncApi {
getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter, getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter,
).then(function(filterId) { ).then(function(filterId) {
qps.filter = filterId; qps.filter = filterId;
return client.http.authedRequest( return client.http.authedRequest<any>( // TODO types
undefined, "GET", "/sync", qps, undefined, localTimeoutMs, undefined, Method.Get, "/sync", qps as any, undefined, localTimeoutMs,
); );
}).then((data) => { }).then((data) => {
let leaveRooms = []; let leaveRooms = [];
if (data.rooms && data.rooms.leave) { if (data.rooms?.leave) {
leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave); leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave);
} }
const rooms = []; const rooms = [];
@@ -400,9 +406,10 @@ export class SyncApi {
} }
// FIXME: gut wrenching; hard-coded timeout values // FIXME: gut wrenching; hard-coded timeout values
this.client.http.authedRequest(undefined, "GET", "/events", { // TODO types
this.client.http.authedRequest<any>(undefined, Method.Get, "/events", {
room_id: peekRoom.roomId, room_id: peekRoom.roomId,
timeout: 30 * 1000, timeout: String(30 * 1000),
from: token, from: token,
}, undefined, 50 * 1000).then((res) => { }, undefined, 50 * 1000).then((res) => {
if (this._peekRoom !== peekRoom) { if (this._peekRoom !== peekRoom) {
@@ -865,8 +872,8 @@ export class SyncApi {
private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): IRequestPromise<ISyncResponse> { private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): IRequestPromise<ISyncResponse> {
const qps = this.getSyncParams(syncOptions, syncToken); const qps = this.getSyncParams(syncOptions, syncToken);
return this.client.http.authedRequest( return this.client.http.authedRequest( // TODO types
undefined, "GET", "/sync", qps, undefined, undefined, Method.Get, "/sync", qps as any, undefined,
qps.timeout + BUFFER_PERIOD_MS, qps.timeout + BUFFER_PERIOD_MS,
); );
} }
@@ -901,7 +908,7 @@ export class SyncApi {
}; };
if (this.opts.disablePresence) { if (this.opts.disablePresence) {
qps.set_presence = "offline"; qps.set_presence = SetPresence.Offline;
} }
if (syncToken) { if (syncToken) {
@@ -924,7 +931,7 @@ export class SyncApi {
return qps; return qps;
} }
private onSyncError(err: Error, syncOptions: ISyncOptions): void { private onSyncError(err: MatrixError, syncOptions: ISyncOptions): void {
if (!this.running) { if (!this.running) {
debuglog("Sync no longer running: exiting"); debuglog("Sync no longer running: exiting");
if (this.connectionReturnedDefer) { if (this.connectionReturnedDefer) {
@@ -1477,7 +1484,7 @@ export class SyncApi {
this.client.http.request( this.client.http.request(
undefined, // callback undefined, // callback
"GET", "/_matrix/client/versions", Method.Get, "/_matrix/client/versions",
undefined, // queryParams undefined, // queryParams
undefined, // data undefined, // data
{ {

View File

@@ -1424,6 +1424,11 @@
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== 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": "@types/graceful-fs@^4.1.2":
version "4.1.5" version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"